#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"
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 AVL_TREE(map_aliases, avl_strcmp, false, NULL);
static uint32_t next_timeout;
-static uint8_t qosify_dscp_default[2] = { 0xff, 0xff };
-int qosify_map_timeout = 3600;
+static struct qosify_dscp_val qosify_dscp_default[2] = {
+ { 0xff, 0xff },
+ { 0xff, 0xff }
+};
+int qosify_map_timeout;
+int qosify_active_timeout;
struct qosify_config config;
struct qosify_map_file {
char filename[];
};
+struct qosify_map_alias {
+ struct avl_node avl;
+ struct qosify_dscp_val value;
+};
+
static const struct {
const char *name;
const char *type_name;
{ "EF", 46 },
{ "VA", 44 },
{ "LE", 1 },
+ { "DF", 0 },
};
static void qosify_map_timer_cb(struct uloop_timeout *t)
bpf_map_delete_elem(fd, &key);
}
-static void __qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
+static void __qosify_map_set_dscp_default(enum qosify_map_id id, struct qosify_dscp_val *val)
{
struct qosify_map_data data = {
.id = id,
int fd = qosify_map_fds[id];
int i;
- val |= QOSIFY_DSCP_DEFAULT_FLAG;
+ val->ingress |= QOSIFY_DSCP_DEFAULT_FLAG;
+ val->egress |= QOSIFY_DSCP_DEFAULT_FLAG;
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);
+ bpf_map_update_elem(fd, &data.addr, val, BPF_ANY);
}
}
-void qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
+void qosify_map_set_dscp_default(enum qosify_map_id id, struct qosify_dscp_val val)
{
bool udp;
else
return;
- if (qosify_dscp_default[udp] == val)
+ if (!memcmp(&qosify_dscp_default[udp], &val, sizeof(val)))
return;
qosify_dscp_default[udp] = val;
- __qosify_map_set_dscp_default(id, val);
+ __qosify_map_set_dscp_default(id, &qosify_dscp_default[udp]);
}
int qosify_map_init(void)
{
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;
}
static void __qosify_map_set_entry(struct qosify_map_data *data)
{
int fd = qosify_map_fds[data->id];
+ struct qosify_dscp_val prev_dscp = { 0xff, 0xff };
struct qosify_map_entry *e;
bool file = data->file;
int32_t delta = 0;
- bool add = data->dscp != 0xff;
- uint8_t prev_dscp = 0xff;
+ bool add = data->dscp.ingress != 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 (memcmp(&e->data.dscp, &prev_dscp, sizeof(prev_dscp)) != 0 &&
+ 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,
+ struct qosify_dscp_val 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;
+}
+
+int qosify_map_dscp_value(const char *val, struct qosify_dscp_val *dscp_val)
+{
+ struct qosify_map_alias *alias;
+ bool fallback = false;
+
+ if (*val == '+') {
+ fallback = true;
+ val++;
+ }
+
+ alias = avl_find_element(&map_aliases, val, alias, avl);
+ if (alias) {
+ *dscp_val = alias->value;
+ } else {
+ if (__qosify_map_dscp_value(val, &dscp_val->egress))
+ return -1;
+
+ dscp_val->ingress = dscp_val->egress;
+ }
+
+ if (fallback) {
+ dscp_val->ingress |= (1 << 6);
+ dscp_val->egress |= (1 << 6);
+ }
+
+ return 0;
}
static void
qosify_map_parse_line(char *str)
{
const char *key, *value;
- int dscp;
+ struct qosify_dscp_val 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;
}
void qosify_map_reset_config(void)
{
+ struct qosify_dscp_val val = {};
+
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_set_dscp_default(CL_MAP_TCP_PORTS, val);
+ qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, val);
qosify_map_timeout = 3600;
+ qosify_active_timeout = 300;
memset(&config, 0, sizeof(config));
- config.dscp_prio = 0xff;
- config.dscp_bulk = 0xff;
- config.dscp_icmp = 0xff;
+ config.dscp_prio.ingress = 0xff;
+ config.dscp_prio.egress = 0xff;
+ config.dscp_bulk.ingress = 0xff;
+ config.dscp_bulk.egress = 0xff;
+ config.dscp_icmp.ingress = 0xff;
+ config.dscp_icmp.egress = 0xff;
}
void qosify_map_reload(void)
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;
+
+ 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_ingress", e->data.dscp.ingress);
+ blobmsg_add_dscp(b, "dscp_egress", e->data.dscp.egress);
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 int
+qosify_map_create_alias(struct blob_attr *attr)
+{
+ struct qosify_map_alias *alias;
+ enum {
+ MAP_ALIAS_INGRESS,
+ MAP_ALIAS_EGRESS,
+ __MAP_ALIAS_MAX
+ };
+ static const struct blobmsg_policy policy[__MAP_ALIAS_MAX] = {
+ [MAP_ALIAS_INGRESS] = { .type = BLOBMSG_TYPE_STRING },
+ [MAP_ALIAS_EGRESS] = { .type = BLOBMSG_TYPE_STRING },
+ };
+ struct blob_attr *tb[__MAP_ALIAS_MAX];
+ const char *name;
+ char *name_buf;
+
+ if (blobmsg_check_array(attr, BLOBMSG_TYPE_STRING) != 2)
+ return -1;
+
+ blobmsg_parse_array(policy, __MAP_ALIAS_MAX, tb,
+ blobmsg_data(attr), blobmsg_len(attr));
+
+ if (!tb[MAP_ALIAS_INGRESS] || !tb[MAP_ALIAS_EGRESS])
+ return -1;
+
+ name = blobmsg_name(attr);
+ alias = calloc_a(sizeof(*alias), &name_buf, strlen(name) + 1);
+ alias->avl.key = strcpy(name_buf, name);
+ if (__qosify_map_dscp_value(blobmsg_get_string(tb[MAP_ALIAS_INGRESS]),
+ &alias->value.ingress) ||
+ __qosify_map_dscp_value(blobmsg_get_string(tb[MAP_ALIAS_EGRESS]),
+ &alias->value.egress) ||
+ avl_insert(&map_aliases, &alias->avl)) {
+ free(alias);
+ return -1;
+ }
+
+ return 0;
+}
+
+void qosify_map_set_aliases(struct blob_attr *val)
+{
+ struct qosify_map_alias *alias, *tmp;
+ struct blob_attr *cur;
+ int rem;
+
+ avl_remove_all_elements(&map_aliases, alias, avl, tmp)
+ free(alias);
+
+ blobmsg_for_each_attr(cur, val, rem)
+ qosify_map_create_alias(cur);
+}
+
void qosify_map_update_config(void)
{
int fd = qosify_map_fds[CL_MAP_CONFIG];