map: default to fnmatch matching for dns patterns. support regex via leading /
[project/qosify.git] / map.c
diff --git a/map.c b/map.c
index 2eacfcdc40b67ac391913aef7bd798b4da3bcfb1..52f4337a3142aa158d25c7542e0f4d9f35a2073a 100644 (file)
--- a/map.c
+++ b/map.c
@@ -9,6 +9,7 @@
 #include <ctype.h>
 #include <stdlib.h>
 #include <time.h>
+#include <fnmatch.h>
 
 #include <libubox/uloop.h>
 
@@ -21,7 +22,8 @@ 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;
+int qosify_map_timeout;
+int qosify_active_timeout;
 struct qosify_config config;
 
 struct qosify_map_file {
@@ -38,6 +40,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_DNS] = { "dns", "dns" },
 };
 
 static const struct {
@@ -66,6 +69,8 @@ static const struct {
        { "AF43", 38 },
        { "EF", 46 },
        { "VA", 44 },
+       { "LE", 1 },
+       { "DF", 0 },
 };
 
 static void qosify_map_timer_cb(struct uloop_timeout *t)
@@ -169,7 +174,7 @@ int qosify_map_init(void)
 {
        int i;
 
-       for (i = 0; i < ARRAY_SIZE(qosify_map_fds); i++) {
+       for (i = 0; i < CL_MAP_DNS; i++) {
                qosify_map_fds[i] = qosify_map_get_fd(i);
                if (qosify_map_fds[i] < 0)
                        return -1;
@@ -210,9 +215,43 @@ static int qosify_map_entry_cmp(const void *k1, const void *k2, void *ptr)
        if (d1->id != d2->id)
                return d2->id - d1->id;
 
+       if (d1->id == CL_MAP_DNS)
+               return strcmp(d1->addr.dns.pattern, d2->addr.dns.pattern);
+
        return memcmp(&d1->addr, &d2->addr, sizeof(d1->addr));
 }
 
+static struct qosify_map_entry *
+__qosify_map_alloc_entry(struct qosify_map_data *data)
+{
+       struct qosify_map_entry *e;
+       char *pattern;
+       char *c;
+
+       if (data->id < CL_MAP_DNS) {
+               e = calloc(1, sizeof(*e));
+               memcpy(&e->data.addr, &data->addr, sizeof(e->data.addr));
+
+               return 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;
+
+       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;
+       }
+
+       return e;
+}
+
 static void __qosify_map_set_entry(struct qosify_map_data *data)
 {
        int fd = qosify_map_fds[data->id];
@@ -227,10 +266,12 @@ static void __qosify_map_set_entry(struct qosify_map_data *data)
                if (!add)
                        return;
 
-               e = calloc(1, sizeof(*e));
+               e = __qosify_map_alloc_entry(data);
+               if (!e)
+                       return;
+
                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;
@@ -250,8 +291,14 @@ static void __qosify_map_set_entry(struct qosify_map_data *data)
                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 (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) {
@@ -320,6 +367,9 @@ 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;
+               break;
        case CL_MAP_TCP_PORTS:
        case CL_MAP_UDP_PORTS:
                return qosify_map_set_port(&data, str);
@@ -401,6 +451,8 @@ qosify_map_parse_line(char *str)
        if (dscp < 0)
                return;
 
+       if (!strncmp(key, "dns:", 4))
+               qosify_map_set_entry(CL_MAP_DNS, true, key + 4, dscp);
        if (!strncmp(key, "tcp:", 4))
                qosify_map_set_entry(CL_MAP_TCP_PORTS, true, key + 4, dscp);
        else if (!strncmp(key, "udp:", 4))
@@ -487,6 +539,7 @@ void qosify_map_reset_config(void)
        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;
@@ -511,10 +564,34 @@ static void qosify_map_free_entry(struct qosify_map_entry *e)
        int fd = qosify_map_fds[e->data.id];
 
        avl_delete(&map_data, &e->avl);
-       bpf_map_delete_elem(fd, &e->data.addr);
+       if (e->data.id < CL_MAP_DNS)
+               bpf_map_delete_elem(fd, &e->data.addr);
        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;
@@ -527,6 +604,9 @@ void qosify_map_gc(void)
 
                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;
@@ -548,6 +628,61 @@ 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)
+{
+       struct qosify_map_data data = {
+               .id = CL_MAP_DNS,
+               .addr.dns.pattern = "",
+       };
+       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)
+               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++)
+               *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 (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;
+               data.dscp = e->data.dscp;
+               __qosify_map_set_entry(&data);
+               qosify_map_timeout = prev_timeout;
+       }
+
+       return 0;
+}
+
+
 void qosify_map_dump(struct blob_buf *b)
 {
        struct qosify_map_entry *e;
@@ -583,22 +718,25 @@ void qosify_map_dump(struct blob_buf *b)
 
                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));
+                       blobmsg_printf(b, "addr", "%d", ntohs(e->data.addr.port));
                        break;
                case CL_MAP_IPV4_ADDR:
                case CL_MAP_IPV6_ADDR:
+                       buf = blobmsg_alloc_string_buffer(b, "addr", buf_len);
                        af = e->data.id == CL_MAP_IPV6_ADDR ? AF_INET6 : AF_INET;
                        inet_ntop(af, &e->data.addr, buf, buf_len);
+                       blobmsg_add_string_buffer(b);
+                       break;
+               case CL_MAP_DNS:
+                       blobmsg_add_string(b, "addr", e->data.addr.dns.pattern);
                        break;
                default:
                        *buf = 0;
                        break;
                }
-               blobmsg_add_string_buffer(b);
                blobmsg_close_table(b, c);
        }
        blobmsg_close_array(b, a);