qosify: add support for keeping stats
[project/qosify.git] / map.c
diff --git a/map.c b/map.c
index 7757551b7d954d3ca811ae1786492f1327dc8b9a..64b481805a8324dd842f7c486ad0b41d7c4cc616 100644 (file)
--- a/map.c
+++ b/map.c
 #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;
 int qosify_active_timeout;
 struct qosify_config config;
+struct qosify_flow_config flow_config;
+static uint32_t map_dns_seq;
 
 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;
@@ -41,6 +52,7 @@ static const struct {
        [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" },
 };
 
@@ -61,7 +73,7 @@ static const struct {
        { "AF13", 14 },
        { "AF21", 18 },
        { "AF22", 20 },
-       { "AF22", 22 },
+       { "AF23", 22 },
        { "AF31", 26 },
        { "AF32", 28 },
        { "AF33", 30 },
@@ -130,7 +142,7 @@ 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)
+       while (bpf_map_get_next_key(fd, &key, &key) == 0)
                bpf_map_delete_elem(fd, &key);
 }
 
@@ -139,11 +151,31 @@ 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];
+       struct qosify_class class = {
+               .val.ingress = val,
+               .val.egress = val,
+       };
+       uint32_t key;
+       int fd;
        int i;
 
-       val |= QOSIFY_DSCP_DEFAULT_FLAG;
+       if (!(val & QOSIFY_DSCP_CLASS_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];
+
+               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))
@@ -164,11 +196,14 @@ void qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
        else
                return;
 
-       if (qosify_dscp_default[udp] == val)
-               return;
+       if (val != 0xff) {
+               if (qosify_dscp_default[udp] == val)
+                       return;
+
+               qosify_dscp_default[udp] = val;
+       }
 
-       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)
@@ -253,14 +288,14 @@ __qosify_map_alloc_entry(struct qosify_map_data *data)
        return e;
 }
 
-static void __qosify_map_set_entry(struct qosify_map_data *data)
+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;
+       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) {
@@ -301,6 +336,9 @@ static void __qosify_map_set_entry(struct qosify_map_data *data)
                bpf_map_update_elem(fd, &data->addr, &val, BPF_ANY);
        }
 
+       if (data->id == CL_MAP_DNS)
+               e->data.addr.dns.seq = ++map_dns_seq;
+
        if (add) {
                if (qosify_map_timeout == ~0 || file) {
                        e->timeout = ~0;
@@ -359,7 +397,8 @@ qosify_map_fill_ip(struct qosify_map_data *data, const char *str)
        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,
@@ -370,6 +409,8 @@ int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str, uint
        switch (id) {
        case CL_MAP_DNS:
                data.addr.dns.pattern = str;
+               if (str[-2] == 'c')
+                       data.addr.dns.only_cname = 1;
                break;
        case CL_MAP_TCP_PORTS:
        case CL_MAP_UDP_PORTS:
@@ -388,11 +429,12 @@ int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str, uint
        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;
@@ -406,7 +448,42 @@ int qosify_map_dscp_value(const char *val)
        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
@@ -435,7 +512,7 @@ static void
 qosify_map_parse_line(char *str)
 {
        const char *key, *value;
-       int dscp;
+       uint8_t dscp;
 
        str = str_skip(str, true);
        key = str;
@@ -448,12 +525,13 @@ qosify_map_parse_line(char *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_DNS, true, key + 4, dscp);
+       if (!strncmp(key, "dns_q:", 6) || !strncmp(key, "dns_c:", 6))
+               qosify_map_set_entry(CL_MAP_DNS, true, key + 6, dscp);
        if (!strncmp(key, "tcp:", 4))
                qosify_map_set_entry(CL_MAP_TCP_PORTS, true, key + 4, dscp);
        else if (!strncmp(key, "udp:", 4))
@@ -532,6 +610,7 @@ static void qosify_map_reset_file_entries(void)
 {
        struct qosify_map_entry *e;
 
+       map_dns_seq = 0;
        avl_for_each_element(&map_data, e, avl)
                e->data.file = false;
 }
@@ -557,8 +636,8 @@ void qosify_map_reset_config(void)
        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;
 }
 
@@ -572,6 +651,9 @@ void qosify_map_reload(void)
                __qosify_map_load_file(f->filename);
 
        qosify_map_gc();
+
+       qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, 0xff);
+       qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, 0xff);
 }
 
 static void qosify_map_free_entry(struct qosify_map_entry *e)
@@ -643,31 +725,18 @@ void qosify_map_gc(void)
        uloop_timeout_set(&qosify_map_timer, timeout * 1000);
 }
 
-
-int qosify_map_add_dns_host(char *host, const char *addr, const char *type, int ttl)
+int qosify_map_lookup_dns_entry(char *host, bool cname, uint8_t *dscp, uint32_t *seq)
 {
        struct qosify_map_data data = {
                .id = CL_MAP_DNS,
                .addr.dns.pattern = "",
        };
        struct qosify_map_entry *e;
-       int prev_timeout = qosify_map_timeout;
+       bool ret = -1;
        char *c;
 
        e = avl_find_ge_element(&map_data, &data, e, avl);
        if (!e)
-               return 0;
-
-       memset(&data, 0, sizeof(data));
-       data.user = true;
-       if (!strcmp(type, "A"))
-               data.id = CL_MAP_IPV4_ADDR;
-       else if (!strcmp(type, "AAAA"))
-               data.id = CL_MAP_IPV6_ADDR;
-       else
-               return 0;
-
-       if (qosify_map_fill_ip(&data, addr))
                return -1;
 
        for (c = host; *c; c++)
@@ -677,7 +746,10 @@ int qosify_map_add_dns_host(char *host, const char *addr, const char *type, int
                regex_t *regex = &e->data.addr.dns.regex;
 
                if (e->data.id != CL_MAP_DNS)
-                       return 0;
+                       break;
+
+               if (!cname && e->data.addr.dns.only_cname)
+                       continue;
 
                if (e->data.addr.dns.pattern[0] == '/') {
                        if (regexec(regex, host, 0, NULL, 0) != 0)
@@ -687,16 +759,73 @@ int qosify_map_add_dns_host(char *host, const char *addr, const char *type, int
                                continue;
                }
 
-               if (ttl)
-                       qosify_map_timeout = ttl;
-               data.dscp = e->data.dscp;
-               __qosify_map_set_entry(&data);
-               qosify_map_timeout = prev_timeout;
+               if (*dscp == 0xff || e->data.addr.dns.seq < *seq) {
+                       *dscp = e->data.dscp;
+                       *seq = e->data.addr.dns.seq;
+               }
+               ret = 0;
        }
 
+       return ret;
+}
+
+
+int qosify_map_add_dns_host(char *host, const char *addr, const char *type, int ttl)
+{
+       struct qosify_map_data data = {
+               .dscp = 0xff
+       };
+       int prev_timeout = qosify_map_timeout;
+       uint32_t lookup_seq = 0;
+
+       if (qosify_map_lookup_dns_entry(host, false, &data.dscp, &lookup_seq))
+               return 0;
+
+       data.user = true;
+       if (!strcmp(type, "A"))
+               data.id = CL_MAP_IPV4_ADDR;
+       else if (!strcmp(type, "AAAA"))
+               data.id = CL_MAP_IPV6_ADDR;
+       else
+               return 0;
+
+       if (qosify_map_fill_ip(&data, addr))
+               return -1;
+
+       if (ttl)
+               qosify_map_timeout = ttl;
+       __qosify_map_set_entry(&data);
+       qosify_map_timeout = prev_timeout;
+
        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)
 {
@@ -727,9 +856,7 @@ 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);
 
@@ -749,7 +876,6 @@ void qosify_map_dump(struct blob_buf *b)
                        blobmsg_add_string(b, "addr", e->data.addr.dns.pattern);
                        break;
                default:
-                       *buf = 0;
                        break;
                }
                blobmsg_close_table(b, c);
@@ -757,6 +883,201 @@ void qosify_map_dump(struct blob_buf *b)
        blobmsg_close_array(b, a);
 }
 
+void qosify_map_stats(struct blob_buf *b, bool reset)
+{
+       struct qosify_class data;
+       uint32_t i;
+
+       for (i = 0; i < ARRAY_SIZE(map_class); i++) {
+               void *c;
+
+               if (!map_class[i])
+                       continue;
+
+               if (bpf_map_lookup_elem(qosify_map_fds[CL_MAP_CLASS], &i, &data) < 0)
+                       continue;
+
+               c = blobmsg_open_table(b, map_class[i]->name);
+               blobmsg_add_u64(b, "packets", data.packets);
+               blobmsg_close_table(b, c);
+
+               if (!reset)
+                       continue;
+
+               data.packets = 0;
+               bpf_map_update_elem(qosify_map_fds[CL_MAP_CLASS], &i, &data, BPF_ANY);
+       }
+}
+
+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];