Commit 5df67dae authored by Mark Karpilovskij's avatar Mark Karpilovskij

mod-geoip: optimize query processing speed

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.
parent c9f61f99
/* 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
}
/* 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);
/* 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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment