loader/interface: attach bpf program directly using netlink
[project/qosify.git] / map.c
diff --git a/map.c b/map.c
index 466ad3f4016135a735049cf2ae2f4a284019f233..f88848e4db38266b1bbf4836955f91fb196c9935 100644 (file)
--- a/map.c
+++ b/map.c
 
 #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 AVL_TREE(map_aliases, avl_strcmp, false, NULL);
+static struct qosify_map_class *map_class[QOSIFY_MAX_CLASS_ENTRIES];
 static uint32_t next_timeout;
-static struct qosify_dscp_val qosify_dscp_default[2] = {
-       { 0xff, 0xff },
-       { 0xff, 0xff }
-};
+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_alias {
-       struct avl_node avl;
-       struct qosify_dscp_val value;
+struct qosify_map_class {
+       const char *name;
+       struct qosify_class data;
 };
 
 static const struct {
@@ -51,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" },
 };
 
@@ -71,7 +73,7 @@ static const struct {
        { "AF13", 14 },
        { "AF21", 18 },
        { "AF22", 20 },
-       { "AF22", 22 },
+       { "AF23", 22 },
        { "AF31", 26 },
        { "AF32", 28 },
        { "AF33", 30 },
@@ -144,27 +146,46 @@ static void qosify_map_clear_list(enum qosify_map_id id)
                bpf_map_delete_elem(fd, &key);
 }
 
-static void __qosify_map_set_dscp_default(enum qosify_map_id id, struct qosify_dscp_val *val)
+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->flags |= QOSIFY_VAL_FLAG_PRIO_CHECK |
-                     QOSIFY_VAL_FLAG_BULK_CHECK;
+       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))
                        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, struct qosify_dscp_val val)
+void qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
 {
        bool udp;
 
@@ -179,7 +200,7 @@ void qosify_map_set_dscp_default(enum qosify_map_id id, struct qosify_dscp_val v
                return;
 
        qosify_dscp_default[udp] = val;
-       __qosify_map_set_dscp_default(id, &qosify_dscp_default[udp]);
+       __qosify_map_set_dscp_default(id, val);
 }
 
 int qosify_map_init(void)
@@ -264,14 +285,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_dscp_val prev_dscp = { 0xff, 0xff };
        struct qosify_map_entry *e;
        bool file = data->file;
+       uint8_t prev_dscp = 0xff;
        int32_t delta = 0;
-       bool add = data->dscp.ingress != 0xff;
+       bool add = data->dscp != 0xff;
 
        e = avl_find_element(&map_data, data, e, avl);
        if (!e) {
@@ -303,8 +324,7 @@ static void __qosify_map_set_entry(struct qosify_map_data *data)
                e->data.dscp = e->data.file_dscp;
        }
 
-       if (memcmp(&e->data.dscp, &prev_dscp, sizeof(prev_dscp)) != 0 &&
-           data->id < CL_MAP_DNS) {
+       if (e->data.dscp != prev_dscp && data->id < CL_MAP_DNS) {
                struct qosify_ip_map_val val = {
                        .dscp = e->data.dscp,
                        .seen = 1,
@@ -313,6 +333,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;
@@ -372,7 +395,7 @@ qosify_map_fill_ip(struct qosify_map_data *data, const char *str)
 }
 
 int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str,
-                        struct qosify_dscp_val dscp)
+                        uint8_t dscp)
 {
        struct qosify_map_data data = {
                .id = id,
@@ -383,6 +406,8 @@ int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str,
        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:
@@ -425,30 +450,35 @@ __qosify_map_dscp_value(const char *val, uint8_t *dscp_val)
        return 0;
 }
 
-int qosify_map_dscp_value(const char *val, struct qosify_dscp_val *dscp_val)
+static int
+qosify_map_check_class(const char *val, uint8_t *dscp_val)
 {
-       struct qosify_map_alias *alias;
-       bool fallback = false;
+       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 = true;
+               fallback = QOSIFY_DSCP_FALLBACK_FLAG;
                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))
+       if (qosify_map_check_class(val, dscp_val) &&
+           __qosify_map_dscp_value(val, dscp_val))
                        return -1;
 
-               dscp_val->ingress = dscp_val->egress;
-       }
-
-       if (fallback) {
-               dscp_val->ingress |= (1 << 6);
-               dscp_val->egress |= (1 << 6);
-       }
+       *dscp_val |= fallback;
 
        return 0;
 }
@@ -479,7 +509,7 @@ static void
 qosify_map_parse_line(char *str)
 {
        const char *key, *value;
-       struct qosify_dscp_val dscp;
+       uint8_t dscp;
 
        str = str_skip(str, true);
        key = str;
@@ -497,6 +527,8 @@ qosify_map_parse_line(char *str)
 
        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))
@@ -575,6 +607,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;
 }
@@ -593,21 +626,16 @@ void qosify_map_clear_files(void)
 
 void qosify_map_reset_config(void)
 {
-       struct qosify_dscp_val val = {};
-
        qosify_map_clear_files();
-       qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, val);
-       qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, val);
+       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.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;
+       flow_config.dscp_prio = 0xff;
+       flow_config.dscp_bulk = 0xff;
+       config.dscp_icmp = 0xff;
 }
 
 void qosify_map_reload(void)
@@ -691,31 +719,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++)
@@ -725,7 +740,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)
@@ -735,13 +753,44 @@ 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;
 }
 
@@ -751,6 +800,21 @@ 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);
@@ -786,8 +850,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);
 
-               blobmsg_add_dscp(b, "dscp_ingress", e->data.dscp.ingress);
-               blobmsg_add_dscp(b, "dscp_egress", e->data.dscp.egress);
+               blobmsg_add_dscp(b, "dscp", e->data.dscp);
 
                blobmsg_add_string(b, "type", qosify_map_info[e->data.id].type_name);
 
@@ -814,58 +877,173 @@ void qosify_map_dump(struct blob_buf *b)
        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_alias(struct blob_attr *attr)
+qosify_map_create_class(struct blob_attr *attr)
 {
-       struct qosify_map_alias *alias;
+       struct qosify_map_class *class;
        enum {
-               MAP_ALIAS_INGRESS,
-               MAP_ALIAS_EGRESS,
-               __MAP_ALIAS_MAX
+               MAP_CLASS_INGRESS,
+               MAP_CLASS_EGRESS,
+               __MAP_CLASS_MAX
        };
-       static const struct blobmsg_policy policy[__MAP_ALIAS_MAX] = {
-               [MAP_ALIAS_INGRESS] = { .type = BLOBMSG_TYPE_STRING },
-               [MAP_ALIAS_EGRESS] = { .type = BLOBMSG_TYPE_STRING },
+       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_ALIAS_MAX];
+       struct blob_attr *tb[__MAP_CLASS_MAX];
        const char *name;
        char *name_buf;
+       int32_t slot;
 
-       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));
+       blobmsg_parse(policy, __MAP_CLASS_MAX, tb,
+                     blobmsg_data(attr), blobmsg_len(attr));
 
-       if (!tb[MAP_ALIAS_INGRESS] || !tb[MAP_ALIAS_EGRESS])
+       if (!tb[MAP_CLASS_INGRESS] || !tb[MAP_CLASS_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);
+       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_aliases(struct blob_attr *val)
+void qosify_map_set_classes(struct blob_attr *val)
 {
-       struct qosify_map_alias *alias, *tmp;
+       int fd = qosify_map_fds[CL_MAP_CLASS];
+       struct qosify_class empty_data = {};
        struct blob_attr *cur;
+       int32_t i;
        int rem;
 
-       avl_remove_all_elements(&map_aliases, alias, avl, tmp)
-               free(alias);
+       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_alias(cur);
+               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)