#include <ctype.h>
#include <stdlib.h>
#include <time.h>
+#include <fnmatch.h>
+#include <glob.h>
#include <libubox/uloop.h>
+#include <libubox/avl-cmp.h>
#include "qosify.h"
+struct qosify_map_class;
+
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 struct qosify_map_class *map_class[QOSIFY_MAX_CLASS_ENTRIES];
static uint32_t next_timeout;
static uint8_t qosify_dscp_default[2] = { 0xff, 0xff };
-int qosify_map_timeout = 3600;
+int qosify_map_timeout;
+int qosify_active_timeout;
struct qosify_config config;
+struct qosify_flow_config flow_config;
struct qosify_map_file {
struct list_head list;
char filename[];
};
+struct qosify_map_class {
+ const char *name;
+ struct qosify_class data;
+};
+
static const struct {
const char *name;
const char *type_name;
[CL_MAP_IPV4_ADDR] = { "ipv4_map", "ipv4_addr" },
[CL_MAP_IPV6_ADDR] = { "ipv6_map", "ipv6_addr" },
[CL_MAP_CONFIG] = { "config", "config" },
+ [CL_MAP_CLASS] = { "class_map", "class" },
[CL_MAP_DNS] = { "dns", "dns" },
};
{ "EF", 46 },
{ "VA", 44 },
{ "LE", 1 },
+ { "DF", 0 },
};
static void qosify_map_timer_cb(struct uloop_timeout *t)
struct qosify_map_data data = {
.id = id,
};
- int fd = qosify_map_fds[id];
+ struct qosify_class class = {
+ .val.ingress = val,
+ .val.egress = val,
+ };
+ uint32_t key;
+ int fd;
int i;
- val |= QOSIFY_DSCP_DEFAULT_FLAG;
+ if (id == CL_MAP_TCP_PORTS)
+ key = QOSIFY_MAX_CLASS_ENTRIES;
+ else if (id == CL_MAP_UDP_PORTS)
+ key = QOSIFY_MAX_CLASS_ENTRIES + 1;
+ else
+ return;
+ fd = qosify_map_fds[CL_MAP_CLASS];
+ if (val & QOSIFY_DSCP_CLASS_FLAG) {
+ uint8_t fallback = val & QOSIFY_DSCP_FALLBACK_FLAG;
+
+ val &= QOSIFY_DSCP_VALUE_MASK;
+ if (val > ARRAY_SIZE(map_class) || !map_class[val])
+ return;
+
+ class.val.ingress = map_class[val]->data.val.ingress | fallback;
+ class.val.egress = map_class[val]->data.val.egress | fallback;
+ }
+
+ memcpy(&class.config, &flow_config, sizeof(class.config));
+ bpf_map_update_elem(fd, &key, &class, BPF_ANY);
+
+ val = key | QOSIFY_DSCP_CLASS_FLAG;
+ fd = qosify_map_fds[id];
for (i = 0; i < (1 << 16); i++) {
data.addr.port = htons(i);
if (avl_find(&map_data, &data))
else
return;
- if (qosify_dscp_default[udp] == val)
+ if (!memcmp(&qosify_dscp_default[udp], &val, sizeof(val)))
return;
qosify_dscp_default[udp] = val;
{
struct qosify_map_entry *e;
char *pattern;
+ char *c;
if (data->id < CL_MAP_DNS) {
e = calloc(1, sizeof(*e));
e = calloc_a(sizeof(*e), &pattern, strlen(data->addr.dns.pattern) + 1);
strcpy(pattern, data->addr.dns.pattern);
e->data.addr.dns.pattern = pattern;
- if (regcomp(&e->data.addr.dns.regex, pattern,
- REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
+
+ for (c = pattern; *c; c++)
+ *c = tolower(*c);
+
+ if (pattern[0] == '/' &&
+ regcomp(&e->data.addr.dns.regex, pattern + 1,
+ REG_EXTENDED | REG_NOSUB)) {
free(e);
return NULL;
}
int fd = qosify_map_fds[data->id];
struct qosify_map_entry *e;
bool file = data->file;
+ uint8_t prev_dscp = 0xff;
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) {
e->data.dscp = e->data.file_dscp;
}
- if (e->data.dscp != prev_dscp && data->id < CL_MAP_DNS)
- bpf_map_update_elem(fd, &data->addr, &e->data.dscp, BPF_ANY);
+ if (e->data.dscp != prev_dscp && data->id < CL_MAP_DNS) {
+ struct qosify_ip_map_val val = {
+ .dscp = e->data.dscp,
+ .seen = 1,
+ };
+
+ bpf_map_update_elem(fd, &data->addr, &val, BPF_ANY);
+ }
if (add) {
if (qosify_map_timeout == ~0 || file) {
return 0;
}
-int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str, uint8_t dscp)
+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,
return 0;
}
-int qosify_map_dscp_value(const char *val)
+static int
+__qosify_map_dscp_value(const char *val, uint8_t *dscp_val)
{
unsigned long dscp;
- char *err;
bool fallback = false;
+ char *err;
if (*val == '+') {
fallback = true;
if (dscp >= 64)
return -1;
- return dscp + (fallback << 6);
+ *dscp_val = dscp | (fallback << 6);
+
+ return 0;
+}
+
+static int
+qosify_map_check_class(const char *val, uint8_t *dscp_val)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(map_class); i++) {
+ if (map_class[i] && !strcmp(val, map_class[i]->name)) {
+ *dscp_val = i | QOSIFY_DSCP_CLASS_FLAG;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+int qosify_map_dscp_value(const char *val, uint8_t *dscp_val)
+{
+ uint8_t fallback = 0;
+
+ if (*val == '+') {
+ fallback = QOSIFY_DSCP_FALLBACK_FLAG;
+ val++;
+ }
+
+ if (qosify_map_check_class(val, dscp_val) &&
+ __qosify_map_dscp_value(val, dscp_val))
+ return -1;
+
+ *dscp_val |= fallback;
+
+ return 0;
}
static void
qosify_map_parse_line(char *str)
{
const char *key, *value;
- int dscp;
+ uint8_t dscp;
str = str_skip(str, true);
key = str;
str = str_skip(str, true);
value = str;
- dscp = qosify_map_dscp_value(value);
- if (dscp < 0)
+ if (qosify_map_dscp_value(value, &dscp))
return;
if (!strncmp(key, "dns:", 4))
qosify_map_set_entry(CL_MAP_IPV4_ADDR, true, key, dscp);
}
-static int __qosify_map_load_file(const char *file)
+static void
+__qosify_map_load_file_data(FILE *f)
{
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, '#');
qosify_map_parse_line(line);
}
- fclose(f);
+}
+
+static int
+__qosify_map_load_file(const char *file)
+{
+ glob_t gl;
+ FILE *f;
+ int i;
+
+ if (!file)
+ return 0;
+
+ glob(file, 0, NULL, &gl);
+
+ for (i = 0; i < gl.gl_pathc; i++) {
+ f = fopen(file, "r");
+ if (!f)
+ continue;
+
+ __qosify_map_load_file_data(f);
+ fclose(f);
+ }
+
+ globfree(&gl);
return 0;
}
qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, 0);
qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, 0);
qosify_map_timeout = 3600;
+ qosify_active_timeout = 300;
memset(&config, 0, sizeof(config));
- config.dscp_prio = 0xff;
- config.dscp_bulk = 0xff;
+ flow_config.dscp_prio = 0xff;
+ flow_config.dscp_bulk = 0xff;
config.dscp_icmp = 0xff;
}
free(e);
}
+static bool
+qosify_map_entry_refresh_timeout(struct qosify_map_entry *e)
+{
+ struct qosify_ip_map_val val;
+ int fd = qosify_map_fds[e->data.id];
+
+ if (e->data.id != CL_MAP_IPV4_ADDR &&
+ e->data.id != CL_MAP_IPV6_ADDR)
+ return false;
+
+ if (bpf_map_lookup_elem(fd, &e->data.addr, &val))
+ return false;
+
+ if (!val.seen)
+ return false;
+
+ e->timeout = qosify_gettime() + qosify_active_timeout;
+ val.seen = 0;
+ bpf_map_update_elem(fd, &e->data.addr, &val, BPF_ANY);
+
+ return true;
+}
+
void qosify_map_gc(void)
{
struct qosify_map_entry *e, *tmp;
if (e->data.user && e->timeout != ~0) {
cur_timeout = e->timeout - cur_time;
+ if (cur_timeout <= 0 &&
+ qosify_map_entry_refresh_timeout(e))
+ cur_timeout = e->timeout - cur_time;
if (cur_timeout <= 0) {
e->data.user = false;
e->data.dscp = e->data.file_dscp;
}
-int qosify_map_add_dns_host(const char *host, const char *addr, const char *type, int ttl)
+int qosify_map_add_dns_host(char *host, const char *addr, const char *type, int ttl)
{
struct qosify_map_data data = {
.id = CL_MAP_DNS,
};
struct qosify_map_entry *e;
int prev_timeout = qosify_map_timeout;
+ char *c;
e = avl_find_ge_element(&map_data, &data, e, avl);
if (!e)
if (qosify_map_fill_ip(&data, addr))
return -1;
+ for (c = host; *c; c++)
+ *c = tolower(*c);
+
avl_for_element_to_last(&map_data, e, e, avl) {
regex_t *regex = &e->data.addr.dns.regex;
if (e->data.id != CL_MAP_DNS)
return 0;
- if (regexec(regex, host, 0, NULL, 0) != 0)
- continue;
+ if (e->data.addr.dns.pattern[0] == '/') {
+ if (regexec(regex, host, 0, NULL, 0) != 0)
+ continue;
+ } else {
+ if (fnmatch(e->data.addr.dns.pattern, host, 0))
+ continue;
+ }
if (ttl)
qosify_map_timeout = ttl;
return 0;
}
+static void
+blobmsg_add_dscp(struct blob_buf *b, const char *name, uint8_t dscp)
+{
+ int buf_len = 8;
+ char *buf;
+
+ if (dscp & QOSIFY_DSCP_CLASS_FLAG) {
+ const char *val;
+ int idx;
+
+ idx = dscp & QOSIFY_DSCP_VALUE_MASK;
+ if (map_class[idx])
+ val = map_class[idx]->name;
+ else
+ val = "<invalid>";
+
+ blobmsg_printf(b, name, "%s%s",
+ (dscp & QOSIFY_DSCP_FALLBACK_FLAG) ? "+" : "", val);
+ return;
+ }
+
+ buf = blobmsg_alloc_string_buffer(b, name, buf_len);
+ qosify_map_dscp_codepoint_str(buf, buf_len, dscp);
+ blobmsg_add_string_buffer(b);
+}
+
void qosify_map_dump(struct blob_buf *b)
{
blobmsg_add_u8(b, "file", e->data.file);
blobmsg_add_u8(b, "user", e->data.user);
- buf = blobmsg_alloc_string_buffer(b, "dscp", buf_len);
- qosify_map_dscp_codepoint_str(buf, buf_len, e->data.dscp);
- blobmsg_add_string_buffer(b);
+ blobmsg_add_dscp(b, "dscp", e->data.dscp);
blobmsg_add_string(b, "type", qosify_map_info[e->data.id].type_name);
blobmsg_add_string(b, "addr", e->data.addr.dns.pattern);
break;
default:
- *buf = 0;
break;
}
blobmsg_close_table(b, c);
blobmsg_close_array(b, a);
}
+static int32_t
+qosify_map_get_class_id(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(map_class); i++)
+ if (map_class[i] && !strcmp(map_class[i]->name, name))
+ return i;
+
+ for (i = 0; i < ARRAY_SIZE(map_class); i++)
+ if (!map_class[i])
+ return i;
+
+ for (i = 0; i < ARRAY_SIZE(map_class); i++) {
+ if (!(map_class[i]->data.flags & QOSIFY_CLASS_FLAG_PRESENT)) {
+ free(map_class[i]);
+ map_class[i] = NULL;
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+int map_fill_dscp_value(uint8_t *dest, struct blob_attr *attr, bool reset)
+{
+ if (reset)
+ *dest = 0xff;
+
+ if (!attr)
+ return 0;
+
+ if (qosify_map_dscp_value(blobmsg_get_string(attr), dest))
+ return -1;
+
+ return 0;
+}
+
+int map_parse_flow_config(struct qosify_flow_config *cfg, struct blob_attr *attr,
+ bool reset)
+{
+ enum {
+ CL_CONFIG_DSCP_PRIO,
+ CL_CONFIG_DSCP_BULK,
+ CL_CONFIG_BULK_TIMEOUT,
+ CL_CONFIG_BULK_PPS,
+ CL_CONFIG_PRIO_PKT_LEN,
+ __CL_CONFIG_MAX
+ };
+ static const struct blobmsg_policy policy[__CL_CONFIG_MAX] = {
+ [CL_CONFIG_DSCP_PRIO] = { "dscp_prio", BLOBMSG_TYPE_STRING },
+ [CL_CONFIG_DSCP_BULK] = { "dscp_bulk", BLOBMSG_TYPE_STRING },
+ [CL_CONFIG_BULK_TIMEOUT] = { "bulk_trigger_timeout", BLOBMSG_TYPE_INT32 },
+ [CL_CONFIG_BULK_PPS] = { "bulk_trigger_pps", BLOBMSG_TYPE_INT32 },
+ [CL_CONFIG_PRIO_PKT_LEN] = { "prio_max_avg_pkt_len", BLOBMSG_TYPE_INT32 },
+ };
+ struct blob_attr *tb[__CL_CONFIG_MAX];
+ struct blob_attr *cur;
+
+ if (reset)
+ memset(cfg, 0, sizeof(*cfg));
+
+ blobmsg_parse(policy, __CL_CONFIG_MAX, tb, blobmsg_data(attr), blobmsg_len(attr));
+
+ if (map_fill_dscp_value(&cfg->dscp_prio, tb[CL_CONFIG_DSCP_PRIO], reset) ||
+ map_fill_dscp_value(&cfg->dscp_bulk, tb[CL_CONFIG_DSCP_BULK], reset))
+ return -1;
+
+ if ((cur = tb[CL_CONFIG_BULK_TIMEOUT]) != NULL)
+ cfg->bulk_trigger_timeout = blobmsg_get_u32(cur);
+
+ if ((cur = tb[CL_CONFIG_BULK_PPS]) != NULL)
+ cfg->bulk_trigger_pps = blobmsg_get_u32(cur);
+
+ if ((cur = tb[CL_CONFIG_PRIO_PKT_LEN]) != NULL)
+ cfg->prio_max_avg_pkt_len = blobmsg_get_u32(cur);
+
+ return 0;
+}
+
+static int
+qosify_map_create_class(struct blob_attr *attr)
+{
+ struct qosify_map_class *class;
+ enum {
+ MAP_CLASS_INGRESS,
+ MAP_CLASS_EGRESS,
+ __MAP_CLASS_MAX
+ };
+ static const struct blobmsg_policy policy[__MAP_CLASS_MAX] = {
+ [MAP_CLASS_INGRESS] = { "ingress", BLOBMSG_TYPE_STRING },
+ [MAP_CLASS_EGRESS] = { "egress", BLOBMSG_TYPE_STRING },
+ };
+ struct blob_attr *tb[__MAP_CLASS_MAX];
+ const char *name;
+ char *name_buf;
+ int32_t slot;
+
+ blobmsg_parse(policy, __MAP_CLASS_MAX, tb,
+ blobmsg_data(attr), blobmsg_len(attr));
+
+ if (!tb[MAP_CLASS_INGRESS] || !tb[MAP_CLASS_EGRESS])
+ return -1;
+
+ name = blobmsg_name(attr);
+ slot = qosify_map_get_class_id(name);
+ if (slot < 0)
+ return -1;
+
+ class = map_class[slot];
+ if (!class) {
+ class = calloc_a(sizeof(*class), &name_buf, strlen(name) + 1);
+ class->name = strcpy(name_buf, name);
+ map_class[slot] = class;
+ }
+
+ class->data.flags |= QOSIFY_CLASS_FLAG_PRESENT;
+ if (__qosify_map_dscp_value(blobmsg_get_string(tb[MAP_CLASS_INGRESS]),
+ &class->data.val.ingress) ||
+ __qosify_map_dscp_value(blobmsg_get_string(tb[MAP_CLASS_EGRESS]),
+ &class->data.val.egress)) {
+ map_class[slot] = NULL;
+ free(class);
+ return -1;
+ }
+
+ return 0;
+}
+
+void qosify_map_set_classes(struct blob_attr *val)
+{
+ int fd = qosify_map_fds[CL_MAP_CLASS];
+ struct qosify_class empty_data = {};
+ struct blob_attr *cur;
+ int32_t i;
+ int rem;
+
+ for (i = 0; i < ARRAY_SIZE(map_class); i++)
+ if (map_class[i])
+ map_class[i]->data.flags &= ~QOSIFY_CLASS_FLAG_PRESENT;
+
+ blobmsg_for_each_attr(cur, val, rem)
+ qosify_map_create_class(cur);
+
+ for (i = 0; i < ARRAY_SIZE(map_class); i++) {
+ if (map_class[i] &&
+ (map_class[i]->data.flags & QOSIFY_CLASS_FLAG_PRESENT))
+ continue;
+
+ free(map_class[i]);
+ map_class[i] = NULL;
+ }
+
+ blobmsg_for_each_attr(cur, val, rem) {
+ i = qosify_map_get_class_id(blobmsg_name(cur));
+ if (i < 0 || !map_class[i])
+ continue;
+
+ map_parse_flow_config(&map_class[i]->data.config, cur, true);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(map_class); i++) {
+ struct qosify_class *data;
+
+ data = map_class[i] ? &map_class[i]->data : &empty_data;
+ bpf_map_update_elem(fd, &i, data, BPF_ANY);
+ }
+}
+
void qosify_map_update_config(void)
{
int fd = qosify_map_fds[CL_MAP_CONFIG];