]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
mod-geoip: optimize query processing speed
authorMark Karpilovskij <mark.karpilovskij@nic.cz>
Fri, 15 Feb 2019 14:33:00 +0000 (15:33 +0100)
committerMark Karpilovskij <mark.karpilovskij@nic.cz>
Tue, 26 Feb 2019 15:08:00 +0000 (16:08 +0100)
Geo views for a given domain name are sorted when the module is loaded
and the linear iteration over them during query processing
is replaced by a faster binary search.

src/knot/modules/geoip/geodb.c
src/knot/modules/geoip/geodb.h
src/knot/modules/geoip/geoip.c

index 0235352eb2280949b786c1d502085c45d94eeb5e..0fcf99a3ce8fc5b728680e178251d313b1e84a32 100644 (file)
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -188,35 +188,29 @@ int geodb_query(geodb_t *geodb, geodb_data_t *entries, struct sockaddr *remote,
 #endif
 }
 
-bool remote_in_geo(void **geodata, uint32_t *geodata_len, uint16_t geodepth, geodb_data_t *entries)
+void geodb_fill_geodata(geodb_data_t *entries, uint16_t path_cnt,
+                        void **geodata, uint32_t *geodata_len, uint8_t *geodepth)
 {
 #if HAVE_MAXMINDDB
-       for (int i = 0; i < geodepth; i++) {
-               // Nothing to do if current geodata do not specify this key.
-               if (geodata[i] == NULL) {
-                       continue;
-               }
-               if (!entries[i].has_data) {
-                       return false;
-               }
-               switch (entries[i].type) {
-               case MMDB_DATA_TYPE_UTF8_STRING:
-                       if (geodata_len[i] != entries[i].data_size ||
-                           memcmp(geodata[i], entries[i].utf8_string, geodata_len[i]) != 0) {
-                               return false;
-                       }
-                       break;
-               case MMDB_DATA_TYPE_UINT32:
-                       if (*((uint32_t *)geodata[i]) != entries[i].uint32) {
-                               return false;
+       for (int i = 0; i < path_cnt; i++) {
+               if (entries[i].has_data) {
+                       *geodepth = i + 1;
+                       switch (entries[i].type) {
+                       case MMDB_DATA_TYPE_UTF8_STRING:
+                               geodata[i] = (void *)entries[i].utf8_string;
+                               geodata_len[i] = entries[i].data_size;
+                               break;
+                       case MMDB_DATA_TYPE_UINT32:
+                               geodata[i] = (void *)&entries[i].uint32;
+                               geodata_len[i] = sizeof(uint32_t);
+                               break;
+                       default:
+                               assert(0);
+                               break;
                        }
-                       break;
-               default:
-                       return false;
                }
        }
-       return true;
 #else
-       return false;
+       return;
 #endif
 }
index f092980126d12c7fc833348332f9dcb0dfb6ee64..855594b369a2eafa1fda584bbd281e6260c75a44 100644 (file)
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -62,4 +62,5 @@ void geodb_close(geodb_t *geodb);
 int geodb_query(geodb_t *geodb, geodb_data_t *entries, struct sockaddr *remote,
                 geodb_path_t *paths, uint16_t path_cnt, uint16_t *netmask);
 
-bool remote_in_geo(void **geodata, uint32_t *geodata_len, uint16_t geodepth, geodb_data_t *entries);
+void geodb_fill_geodata(geodb_data_t *entries, uint16_t path_cnt,
+                        void **geodata, uint32_t *geodata_len, uint8_t *geodepth);
index 2f90295b49ca27d48d4d814c19d758a86e8428ce..37bc8f1242860efdb410b1a1601579f75773c85d 100644 (file)
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -24,6 +24,7 @@
 #include "libknot/libknot.h"
 #include "contrib/qp-trie/trie.h"
 #include "contrib/ucw/lists.h"
+#include "contrib/macros.h"
 #include "contrib/sockaddr.h"
 #include "contrib/string.h"
 #include "contrib/strtonum.h"
@@ -97,10 +98,14 @@ typedef struct {
        struct sockaddr_storage *subnet;
        uint8_t subnet_prefix;
 
-       void *geodata[GEODB_MAX_DEPTH];
+       void *geodata[GEODB_MAX_DEPTH]; // NULL if '*' is specified in config.
        uint32_t geodata_len[GEODB_MAX_DEPTH];
        uint8_t geodepth;
 
+       // Index of the "parent" in the sorted view list.
+       // Equal to its own index if there is no parent.
+       size_t prev;
+
        size_t count, avail;
        knot_rrset_t *rrsets;
        knot_rrset_t *rrsigs;
@@ -111,6 +116,67 @@ typedef struct {
        geo_view_t *views;
 } geo_trie_val_t;
 
+int geodb_view_cmp(const void *a, const void *b)
+{
+       geo_view_t *va = (geo_view_t *)a;
+       geo_view_t *vb = (geo_view_t *)b;
+
+       int i = 0;
+       while (i < va->geodepth && i < vb->geodepth) {
+               if (va->geodata[i] == NULL) {
+                       if (vb->geodata[i] != NULL) {
+                               return -1;
+                       }
+               } else {
+                       if (vb->geodata[i] == NULL) {
+                               return 1;
+                       }
+                       int len = MIN(va->geodata_len[i], vb->geodata_len[i]);
+                       int ret = memcmp(va->geodata[i], vb->geodata[i], len);
+                       if (ret < 0 || (ret == 0 && vb->geodata_len[i] > len)) {
+                               return -1;
+                       } else if (ret > 0 || (ret == 0 && va->geodata_len[i] > len)) {
+                               return 1;
+                       }
+               }
+               i++;
+       }
+       if (i < va->geodepth) {
+               return 1;
+       }
+       if (i < vb->geodepth) {
+               return -1;
+       }
+       return 0;
+}
+
+int subnet_view_cmp(const void *a, const void *b)
+{
+       geo_view_t *va = (geo_view_t *)a;
+       geo_view_t *vb = (geo_view_t *)b;
+
+       if (va->subnet->ss_family != vb->subnet->ss_family) {
+               return va->subnet->ss_family - vb->subnet->ss_family;
+       }
+
+       int ret = 0;
+       switch (va->subnet->ss_family) {
+       case AF_INET:
+               ret = memcmp(&((struct sockaddr_in *)va->subnet)->sin_addr,
+                            &((struct sockaddr_in *)vb->subnet)->sin_addr,
+                            sizeof(struct in_addr));
+               break;
+       case AF_INET6:
+               ret = memcmp(&((struct sockaddr_in6 *)va->subnet)->sin6_addr,
+                            &((struct sockaddr_in6 *)vb->subnet)->sin6_addr,
+                            sizeof(struct in6_addr));
+       }
+       if (ret == 0) {
+               return va->subnet_prefix - vb->subnet_prefix;
+       }
+       return ret;
+}
+
 static int add_view_to_trie(knot_dname_t *owner, geo_view_t *view, geoip_ctx_t *ctx)
 {
        int ret = KNOT_EOK;
@@ -520,6 +586,85 @@ static void free_geoip_ctx(geoip_ctx_t *ctx)
        free(ctx);
 }
 
+static bool view_strictly_in_view(geo_view_t *view, geo_view_t *in,
+                                  enum operation_mode mode)
+{
+       switch (mode) {
+       case MODE_GEODB:
+               if (in->geodepth >= view->geodepth) {
+                       return false;
+               }
+               for (int i = 0; i < in->geodepth; i++) {
+                       if (in->geodata[i] != NULL) {
+                               if (in->geodata_len[i] != view->geodata_len[i]) {
+                                       return false;
+                               }
+                               if (memcmp(in->geodata[i], view->geodata[i],
+                                          in->geodata_len[i]) != 0) {
+                                       return false;
+                               }
+                       }
+               }
+               return true;
+       case MODE_SUBNET:
+               if (in->subnet_prefix >= view->subnet_prefix) {
+                       return false;
+               }
+               return sockaddr_net_match((struct sockaddr *)view->subnet,
+                                         (struct sockaddr *)in->subnet,
+                                         in->subnet_prefix);
+       default:
+               assert(0);
+               return false;
+       }
+}
+
+static void geo_sort_and_link(geoip_ctx_t *ctx)
+{
+       trie_it_t *it = trie_it_begin(ctx->geo_trie);
+       while (!trie_it_finished(it)) {
+               geo_trie_val_t *val = (geo_trie_val_t *) (*trie_it_val(it));
+               qsort(val->views, val->count, sizeof(geo_view_t),
+                     (ctx->mode == MODE_GEODB) ? geodb_view_cmp : subnet_view_cmp);
+
+               for (int i = 1; i < val->count; i++) {
+                       geo_view_t *cur_view = &val->views[i];
+                       geo_view_t *prev_view = &val->views[i - 1];
+                       cur_view->prev = i;
+                       int prev = i - 1;
+                       do {
+                               if (view_strictly_in_view(cur_view, prev_view, ctx->mode)) {
+                                       cur_view->prev = prev;
+                                       break;
+                               }
+                               if (prev == prev_view->prev) {
+                                       break;
+                               }
+                               prev = prev_view->prev;
+                               prev_view = &val->views[prev];
+                       } while (1);
+               }
+               trie_it_next(it);
+       }
+       trie_it_free(it);
+}
+
+// Return the index of the last lower or equal element or -1 of not exists.
+static int geo_bin_search(geo_view_t *arr, int count, geo_view_t *x,
+                          int (* cmp)(const void *a, const void *b))
+{
+       int l = 0, r = count;
+       while (l < r) {
+               int m = (l + r) / 2;
+               if (cmp(&arr[m], x) <= 0) {
+                       l = m + 1;
+               } else {
+                       r = m;
+               }
+       }
+       return l - 1; // l is the index of first greater element or N if not exists.
+}
+
 static knotd_in_state_t geoip_process(knotd_in_state_t state, knot_pkt_t *pkt,
                                       knotd_qdata_t *qdata, knotd_mod_t *mod)
 {
@@ -562,61 +707,53 @@ static knotd_in_state_t geoip_process(knotd_in_state_t state, knot_pkt_t *pkt,
        knot_rrset_t *cname = NULL;
        knot_rrset_t *cnamesig = NULL;
        uint16_t netmask = 0;
+       geodb_data_t entries[ctx->path_count];
+
+       // Create dummy view and fill it with data about the current remote.
+       geo_view_t dummy = { 0 };
        if (ctx->mode == MODE_SUBNET) {
-               // Find the best subnet containing the remote and queried rrset.
-               uint8_t best_prefix = 0;
-               for (int i = 0; i < data->count; i++) {
-                       geo_view_t *view = &data->views[i];
-                       if ((rr == NULL || view->subnet_prefix > best_prefix) &&
-                           sockaddr_net_match((struct sockaddr *)remote, (struct sockaddr *)view->subnet, view->subnet_prefix)) {
-                               for (int j = 0; j < view->count; j++) {
-                                       if (view->rrsets[j].type == qtype) {
-                                               best_prefix = view->subnet_prefix;
-                                               rr = &view->rrsets[j];
-                                               rrsig = (view->rrsigs) ? &view->rrsigs[j] : NULL;
-                                               break;
-                                       } else if (view->rrsets[j].type == KNOT_RRTYPE_CNAME) {
-                                               best_prefix = view->subnet_prefix;
-                                               cname = &view->rrsets[j];
-                                               cnamesig = (view->rrsigs) ? &view->rrsigs[j] : NULL;
-                                       }
-                               }
-                       }
-               }
-               netmask = best_prefix;
+               dummy.subnet = (struct sockaddr_storage *)remote;
+               dummy.subnet_prefix = (remote->ss_family == AF_INET) ? 32 : 128;
        } else if (ctx->mode == MODE_GEODB) {
-               geodb_data_t entries[ctx->path_count];
-
                int ret = geodb_query(ctx->geodb, entries, (struct sockaddr *)remote,
                                      ctx->paths, ctx->path_count, &netmask);
+               if (ret != 0) {
+                       return state;
+               }
                // MMDB may supply IPv6 prefixes even for IPv4 address, see man libmaxminddb.
                if (remote->ss_family == AF_INET && netmask > 32) {
                        netmask -= 96;
                }
+               geodb_fill_geodata(entries, ctx->path_count,
+                                  dummy.geodata, dummy.geodata_len, &dummy.geodepth);
+       }
 
-               if (ret != 0) {
-                       return state;
+       // Find last lower or equal view.
+       int (* cmp)(const void *, const void *) = (ctx->mode == MODE_GEODB) ?
+                                                 geodb_view_cmp : subnet_view_cmp;
+       int idx = geo_bin_search(data->views, data->count, &dummy, cmp);
+       if (cmp(&dummy, &data->views[idx]) != 0) {
+               idx = data->views[idx].prev;
+               while (!view_strictly_in_view(&dummy, &data->views[idx], ctx->mode)) {
+                       if (idx == data->views[idx].prev) {
+                               // We are at a root and we have found no suitable view.
+                               return state;
+                       }
+                       idx = data->views[idx].prev;
                }
+       }
 
-               uint8_t best_depth = 0;
-               // Find the best geo view containing the remote and queried rrset.
-               for (int i = 0; i < data->count; i++) {
-                       geo_view_t *view = &data->views[i];
-                       if ((rr == NULL || view->geodepth > best_depth) &&
-                           remote_in_geo(view->geodata, view->geodata_len, view->geodepth, entries)) {
-                               for (int j = 0; j < view->count; j++) {
-                                       if (view->rrsets[j].type == qtype) {
-                                               best_depth = view->geodepth;
-                                               rr = &view->rrsets[j];
-                                               rrsig = (view->rrsigs) ? &view->rrsigs[j] : NULL;
-                                               break;
-                                       } else if (view->rrsets[j].type == KNOT_RRTYPE_CNAME) {
-                                               best_depth = view->geodepth;
-                                               cname = &view->rrsets[j];
-                                               cnamesig = (view->rrsigs) ? &view->rrsigs[j] : NULL;
-                                       }
-                               }
-                       }
+       geo_view_t *view = &data->views[idx];
+       if (ctx->mode == MODE_SUBNET) {
+               netmask = view->subnet_prefix;
+       }
+       for (int i = 0; i < view->count; i++) {
+               if (view->rrsets[i].type == qtype) {
+                       rr = &view->rrsets[i];
+                       rrsig = (view->rrsigs) ? &view->rrsigs[i] : NULL;
+               } else if (view->rrsets[i].type == KNOT_RRTYPE_CNAME) {
+                       cname = &view->rrsets[i];
+                       cnamesig = (view->rrsigs) ? &view->rrsigs[i] : NULL;
                }
        }
 
@@ -724,6 +861,9 @@ int geoip_load(knotd_mod_t *mod)
                return ret;
        }
 
+       // Prepare geo views for faster search.
+       geo_sort_and_link(ctx);
+
        knotd_mod_ctx_set(mod, ctx);
 
        return knotd_mod_in_hook(mod, KNOTD_STAGE_PREANSWER, geoip_process);