diff --git a/Knot.files b/Knot.files index 942bed386b05b383bdab90e563bb7baa73df0d59..b00ad8a193368415e138767ae27d59d3feb19edb 100644 --- a/Knot.files +++ b/Knot.files @@ -144,6 +144,7 @@ src/knot/journal/serialization.h src/knot/modules/cookies/cookies.c src/knot/modules/dnsproxy/dnsproxy.c src/knot/modules/dnstap/dnstap.c +src/knot/modules/geoip/geoip.c src/knot/modules/noudp/noudp.c src/knot/modules/onlinesign/nsec_next.c src/knot/modules/onlinesign/nsec_next.h diff --git a/configure.ac b/configure.ac index 0d4f240bb185e9c5f0d92b22db0a3e5d008012ce..fe3db65690f4e6465160b8acc97a800153509490 100644 --- a/configure.ac +++ b/configure.ac @@ -306,6 +306,7 @@ doc_modules="" KNOT_MODULE([cookies], "yes") KNOT_MODULE([dnsproxy], "yes", "non-shareable") KNOT_MODULE([dnstap], "no") +KNOT_MODULE([geoip], "yes") KNOT_MODULE([noudp], "yes") KNOT_MODULE([onlinesign], "yes", "non-shareable") KNOT_MODULE([rrl], "yes") @@ -344,6 +345,41 @@ AM_CONDITIONAL([HAVE_DNSTAP], test "$enable_dnstap" != "no") AM_CONDITIONAL([HAVE_LIBDNSTAP], test "$enable_dnstap" != "no" -o \ "$STATIC_MODULE_dnstap" != "no" -o \ "$SHARED_MODULE_dnstap" != "no") +# MaxMind DB for the GeoIP module +AC_ARG_ENABLE([maxminddb], + AS_HELP_STRING([--enable-maxminddb=auto|yes|no], [enable MaxMind DB [default=auto]]), + [enable_maxminddb="$enableval"], [enable_maxminddb=auto]) + +AS_IF([test "$enable_daemon" = "no"],[enable_maxminddb=no]) +AS_CASE([$enable_maxminddb], + [no],[], + [auto],[PKG_CHECK_MODULES([libmaxminddb], [libmaxminddb], [enable_maxminddb=yes], [enable_maxminddb=no])], + [yes], [PKG_CHECK_MODULES([libmaxminddb], [libmaxminddb])], + [*],[ + save_CFLAGS="$CFLAGS" + save_LIBS="$LIBS" + AS_IF([test "$enable_maxminddb" != ""],[ + LIBS="$LIBS -L$enable_maxminddb" + CFLAGS="$CFLAGS -I$enable_maxminddb/include" + ]) + AC_SEARCH_LIBS([MMDB_open], [maxminddb], [ + AS_IF([test "$enable_maxminddb" != ""], [ + libmaxminddb_CFLAGS="-I$enable_maxminddb/include" + libmaxminddb_LIBS="-L$enable_maxminddb -lmaxminddb" + ],[ + libmaxminddb_CFLAGS="" + libmaxminddb_LIBS="$ac_cv_search_MMDB_open" + ]) + ],[AC_MSG_ERROR("not found in `$enable_maxminddb'")]) + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" + AC_SUBST([libmaxminddb_CFLAGS]) + AC_SUBST([libmaxminddb_LIBS]) + enable_maxminddb=yes + ]) + +AS_IF([test "$enable_maxminddb" = yes], [AC_DEFINE([HAVE_MAXMINDDB], [1], [Define to 1 to enable MaxMind DB.])]) +AM_CONDITIONAL([HAVE_MAXMINDDB], [test "$enable_maxminddb" = yes]) dnl Check for LMDB lmdb_MIN_VERSION_MAJOR=0 @@ -582,6 +618,7 @@ result_msg_base=" Knot DNS $VERSION Fast zone parser: ${enable_fastparser} Utilities with IDN: ${with_libidn} Utilities with Dnstap: ${enable_dnstap} + MaxMind DB support: ${enable_maxminddb} Systemd integration: ${enable_systemd} PKCS #11 support: ${enable_pkcs11} Ed25519 support: ${enable_ed25519} diff --git a/src/knot/Makefile.inc b/src/knot/Makefile.inc index 59e88cf053f281cadaaa8d0cc3cb980d028160c5..55a53887496d879cec0246405b1036d3fc7c6b96 100644 --- a/src/knot/Makefile.inc +++ b/src/knot/Makefile.inc @@ -188,6 +188,7 @@ pkglib_LTLIBRARIES = include $(srcdir)/knot/modules/cookies/Makefile.inc include $(srcdir)/knot/modules/dnsproxy/Makefile.inc include $(srcdir)/knot/modules/dnstap/Makefile.inc +include $(srcdir)/knot/modules/geoip/Makefile.inc include $(srcdir)/knot/modules/noudp/Makefile.inc include $(srcdir)/knot/modules/onlinesign/Makefile.inc include $(srcdir)/knot/modules/rrl/Makefile.inc diff --git a/src/knot/modules/geoip/Makefile.inc b/src/knot/modules/geoip/Makefile.inc new file mode 100644 index 0000000000000000000000000000000000000000..0c4bd05361c7e6f2dcfbfa05496c416bde15b958 --- /dev/null +++ b/src/knot/modules/geoip/Makefile.inc @@ -0,0 +1,14 @@ +knot_modules_geoip_la_SOURCES = knot/modules/geoip/geoip.c +EXTRA_DIST += knot/modules/geoip/geoip.rst + +if STATIC_MODULE_geoip +libknotd_la_SOURCES += $(knot_modules_geoip_la_SOURCES) +libknotd_la_CPPFLAGS += $(libmaxminddb_CFLAGS) +libknotd_la_LIBADD += $(libmaxminddb_LIBS) +endif + +if SHARED_MODULE_geoip +knot_modules_geoip_la_LDFLAGS = $(KNOTD_MOD_LDFLAGS) +knot_modules_geoip_la_CPPFLAGS = $(KNOTD_MOD_CPPFLAGS) +pkglib_LTLIBRARIES += knot/modules/geoip.la +endif diff --git a/src/knot/modules/geoip/geoip.c b/src/knot/modules/geoip/geoip.c new file mode 100644 index 0000000000000000000000000000000000000000..28db285bac16d855dc60743b159bcc8a1d369c13 --- /dev/null +++ b/src/knot/modules/geoip/geoip.c @@ -0,0 +1,686 @@ +/* Copyright (C) 2018 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "knot/include/module.h" +#include "libknot/libknot.h" +#include "contrib/qp-trie/trie.h" +#include "contrib/mempattern.h" +#include "knot/conf/conf.h" +#include "contrib/ucw/lists.h" +#include "contrib/sockaddr.h" +#include "contrib/openbsd/strlcpy.h" +#include "contrib/string.h" +#include "libzscanner/scanner.h" + +// Next dependecies force static module! +#include "knot/dnssec/rrset-sign.h" +#include "knot/dnssec/zone-keys.h" +#include "knot/nameserver/query_module.h" + +#include <stdio.h> +#include <string.h> +#include <arpa/inet.h> + +<<<<<<< HEAD +#if HAVE_MAXMINDDB +#include <maxminddb.h> +#endif + +#define MOD_GEO_CONF_FILE "\x0D""geo-conf-file" +#define MOD_TTL "\x03""ttl" +#define MOD_MODE "\x04""mode" +#define MOD_GEODB_FILE "\x0A""geodb-file" + +// MaxMind DB related constants. +#define ISO_CODE_LEN 2 +======= +#define MOD_CONFIG_FILE "\x0B""config-file" +#define MOD_TTL "\x03""ttl" +#define MOD_MODE "\x04""mode" +#define MOD_GEODB_FILE "\x0A""geodb-file" +#define MOD_GEODB_KEY "\x09""geodb-key" +>>>>>>> fb31d19a2... blbost + +enum operation_mode { + MODE_SUBNET, + MODE_GEODB +}; + +static const knot_lookup_t modes[] = { + { MODE_SUBNET, "subnet" }, + { MODE_GEODB, "geodb" }, + { 0, NULL } +}; + +const yp_item_t geoip_conf[] = { + { MOD_CONFIG_FILE, YP_TSTR, YP_VNONE }, + { MOD_TTL, YP_TINT, YP_VINT = { 0, UINT32_MAX, 60, YP_STIME } }, + { MOD_MODE, YP_TOPT, YP_VOPT = { modes, MODE_SUBNET} }, + { MOD_GEODB_FILE, YP_TSTR, YP_VNONE }, + { MOD_GEODB_KEY, YP_TSTR, YP_VSTR = { "country/iso_code" }, YP_FMULTI }, + { NULL } +}; + +int geoip_conf_check(knotd_conf_check_args_t *args) +{ + knotd_conf_t conf = knotd_conf_check_item(args, MOD_CONFIG_FILE); + if (conf.count == 0) { + args->err_str = "no configuration file specified"; + return KNOT_EINVAL; + } + conf = knotd_conf_check_item(args, MOD_MODE); + if (conf.count == 1 && conf.single.option == MODE_GEODB) { + conf = knotd_conf_check_item(args, MOD_GEODB_FILE); + if (conf.count == 0) { + args->err_str = "no geodb file specified while in geodb mode"; + return KNOT_EINVAL; + } + } + return KNOT_EOK; +} + +typedef struct { + enum operation_mode mode; + uint32_t ttl; + trie_t *geo_trie; + + bool dnssec; + zone_keyset_t keyset; + kdnssec_ctx_t kctx; + +#if HAVE_MAXMINDDB + MMDB_s db; +#endif +} geoip_ctx_t; + +typedef struct { + uint8_t prefix; + in_addr_t addr; +} subnet_t; + +typedef struct { + char country_iso[3]; + char *city; + uint16_t city_len; +} geodata_t; + +typedef struct { + subnet_t subnet; + geodata_t geodata; + size_t count, avail; + knot_rrset_t *rrsets; + knot_rrset_t *rrsigs; +} geo_view_t; + +typedef struct { + size_t count, avail; + geo_view_t *views; +} geo_trie_val_t; + +static int add_view_to_trie(knot_dname_t *owner, geo_view_t view, geoip_ctx_t *ctx) +{ + int ret = KNOT_EOK; + + // Find the node belonging to the owner. + trie_val_t *val = trie_get_ins(ctx->geo_trie, (char *)owner, knot_dname_size(owner)); + geo_trie_val_t *cur_val = *val; + if (cur_val == NULL) { + // Create new node value. + geo_trie_val_t *new_val = calloc(1, sizeof(geo_trie_val_t)); + new_val->avail = 1; + new_val->count = 1; + new_val->views = malloc(sizeof(geo_view_t)); + new_val->views[0] = view; + + // Add new value to trie. + *val = new_val; + } else { + // Double the views array in size if necessary. + if (cur_val->avail >= cur_val->count) { + void *alloc_ret = realloc(cur_val->views, + 2 * cur_val->avail * sizeof(geo_view_t)); + if (alloc_ret == NULL) { + return KNOT_ENOMEM; + } + cur_val->views = alloc_ret; + cur_val->avail *= 2; + } + + // Insert new element. + cur_val->views[cur_val->count++] = view; + } + + return ret; +} + +static bool addr_in_subnet(in_addr_t addr, subnet_t subnet) +{ + uint8_t mask_data[sizeof(in_addr_t)] = { 0 }; + for (int i = 0; i < subnet.prefix / 8; i++) { + mask_data[i] = UINT8_MAX; + } + if (subnet.prefix % 8 != 0) { + mask_data[subnet.prefix / 8] = ((1 << (subnet.prefix % 8)) - 1) << (8 - subnet.prefix % 8); + } + in_addr_t *mask = (in_addr_t *)mask_data; + return (addr & *mask) == subnet.addr; +} + +static bool addr_in_geo(knotd_mod_t *mod, const struct sockaddr *addr, geodata_t geodata, uint16_t *netmask) +{ +#if HAVE_MAXMINDDB + geoip_ctx_t *ctx = (geoip_ctx_t *)knotd_mod_ctx(mod); + + int mmdb_error = 0; + MMDB_lookup_result_s res; + res = MMDB_lookup_sockaddr(&ctx->db, addr, &mmdb_error); + if (mmdb_error != MMDB_SUCCESS) { + knotd_mod_log(mod, LOG_ERR, "a lookup error in MMDB occured"); + return false; + } + if (!res.found_entry) { + return false; + } + + // Set netmask value. + *netmask = res.netmask; + + MMDB_entry_data_s iso_entry; + MMDB_entry_data_s city_entry; + + // Get remote country ISO code. + mmdb_error = MMDB_get_value(&res.entry, &iso_entry, "country", "iso_code", NULL); + if (mmdb_error != MMDB_SUCCESS) { + knotd_mod_log(mod, LOG_ERR, "an error in MMDB occured (country)"); + return false; + } + if (!iso_entry.has_data || iso_entry.type != MMDB_DATA_TYPE_UTF8_STRING + || iso_entry.data_size != ISO_CODE_LEN) { + return false; + } + + // Get remote city name. + mmdb_error = MMDB_get_value(&res.entry, &city_entry, "city", "names", "en", NULL); + if (mmdb_error != MMDB_SUCCESS && + mmdb_error != MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) { + knotd_mod_log(mod, LOG_ERR, "an error in MMDB occured (city)"); + return false; + } + + // Compare geodata. + if (memcmp(geodata.country_iso, iso_entry.utf8_string, ISO_CODE_LEN) != 0) { + return false; + } + if (geodata.city == NULL) { + return true; + } + if (!city_entry.has_data || city_entry.type != MMDB_DATA_TYPE_UTF8_STRING + || geodata.city_len != city_entry.data_size + || memcmp(geodata.city, city_entry.utf8_string, geodata.city_len)) { + return false; + } + return true; +#endif + return false; +} + +static int finalize_geo_view(geo_view_t *view, knot_dname_t *owner, zone_key_t *key, geoip_ctx_t *ctx) +{ + if (view == NULL) { + return KNOT_EOK; + } + + int ret = KNOT_EOK; + if (key != NULL) { + view->rrsigs = malloc(sizeof(knot_rrset_t) * view->count); + if (view->rrsigs == NULL) { + return KNOT_ENOMEM; + } + for (size_t i = 0; i < view->count; i++) { + knot_dname_t *owner_cpy = knot_dname_copy(owner, NULL); + if (owner_cpy == NULL) { + return KNOT_ENOMEM; + } + knot_rrset_init(&view->rrsigs[i], owner_cpy, KNOT_RRTYPE_RRSIG, + KNOT_CLASS_IN, ctx->ttl); + ret = knot_sign_rrset(&view->rrsigs[i], &view->rrsets[i], + key->key, key->ctx, &ctx->kctx, NULL); + if (ret != KNOT_EOK) { + return ret; + } + } + } + + ret = add_view_to_trie(owner, *view, ctx); + return ret; +} + +static int init_geo_view(geo_view_t *view) +{ + if (view == NULL) { + return KNOT_EINVAL; + } + + view->count = 0; + view->avail = 1; + view->rrsigs = NULL; + view->rrsets = malloc(sizeof(knot_rrset_t)); + if (view->rrsets == NULL) { + return KNOT_ENOMEM; + } + return KNOT_EOK; +} + +static void clear_geo_view(geo_view_t *view) +{ + if (view->geodata.city != NULL) { + free(view->geodata.city); + } + for (int j = 0; j < view->count; j++) { + knot_rrset_clear(&view->rrsets[j], NULL); + if (view->rrsigs != NULL) { + knot_rrset_clear(&view->rrsigs[j], NULL); + } + } + free(view->rrsets); + free(view->rrsigs); +} + +static int geo_conf_yparse(knotd_mod_t *mod, geoip_ctx_t *ctx) +{ + int ret = KNOT_EOK; + yp_parser_t *yp = NULL; + zs_scanner_t *scanner = NULL; + knot_dname_t owner_buff[KNOT_DNAME_MAXLEN]; + knot_dname_t *owner = NULL; + geo_view_t *view = NULL; + + yp = malloc(sizeof(yp_parser_t)); + if (yp == NULL) { + return KNOT_ENOMEM; + } + yp_init(yp); + knotd_conf_t conf = knotd_conf_mod(mod, MOD_CONFIG_FILE); + ret = yp_set_input_file(yp, conf.single.string); + if (ret != KNOT_EOK) { + knotd_mod_log(mod, LOG_ERR, "failed to load configuration file"); + goto cleanup; + } + + scanner = malloc(sizeof(zs_scanner_t)); + if (scanner == NULL) { + ret = KNOT_ENOMEM; + goto cleanup; + } + if (zs_init(scanner, NULL, KNOT_CLASS_IN, ctx->ttl) != 0) { + ret = KNOT_EPARSEFAIL; + goto cleanup; + } + + zone_key_t *key = NULL; + if (ctx->dnssec) { + for (size_t i = 0; i < ctx->keyset.count; i++) { + if (ctx->keyset.keys[i].is_zsk) { + key = &ctx->keyset.keys[i]; + } + } + } + + while (1) { + ret = yp_parse(yp); + if (ret == KNOT_EOF) { + ret = finalize_geo_view(view, owner, key, ctx); + goto cleanup; + } + if (ret != KNOT_EOK) { + knotd_mod_log(mod, LOG_ERR, "failed to parse configuration file (%s)", knot_strerror(ret)); + goto cleanup; + } + + if (yp->event != YP_EKEY1) { + ret = finalize_geo_view(view, owner, key, ctx); + if (ret != KNOT_EOK) { + goto cleanup; + } + } + + if (yp->event == YP_EKEY0) { + owner = knot_dname_from_str(owner_buff, yp->key, sizeof(owner_buff)); + if (owner == NULL) { + ret = KNOT_EINVAL; + knotd_mod_log(mod, LOG_ERR, "invalid domain name in config"); + goto cleanup; + } + + char *set_origin = sprintf_alloc("$ORIGIN %s%s\n", yp->key, + (yp->key[yp->key_len-1] == '.') ? "" : "."); + if (set_origin == NULL) { + ret = KNOT_ENOMEM; + goto cleanup; + } + knotd_mod_log(mod, LOG_DEBUG, "%s", set_origin); + + // Set owner as origin for future record parses. + if (zs_set_input_string(scanner, set_origin, strlen(set_origin)) != 0 + || zs_parse_record(scanner) != 0) { + free(set_origin); + ret = KNOT_EPARSEFAIL; + goto cleanup; + } + free(set_origin); + } + + // New geo view description starts. + if (yp->event == YP_EID) { + if (yp->data_len < 4 || yp->data[ISO_CODE_LEN] != ';') { + ret = KNOT_EINVAL; + goto cleanup; + } + + // Initialize new geo view. + free(view); + view = malloc(sizeof(geo_view_t)); + if (view == NULL) { + ret = KNOT_ENOMEM; + goto cleanup; + } + ret = init_geo_view(view); + if (ret != KNOT_EOK) { + goto cleanup; + } + + // Parse geodata/subnet. TODO more generally! + if (ctx->mode == MODE_GEODB) { + size_t copied = strlcpy(view->geodata.country_iso, yp->data, ISO_CODE_LEN + 1); + view->geodata.city_len = yp->data_len - ISO_CODE_LEN - 1; + if ( view->geodata.city_len == 0 || + (view->geodata.city_len == 1 && yp->data[ISO_CODE_LEN + 1] == '*')) { + view->geodata.city = NULL; + } else { + view->geodata.city = malloc(view->geodata.city_len); + copied = strlcpy(view->geodata.city, yp->data + ISO_CODE_LEN + 1, + view->geodata.city_len); + if (copied != view->geodata.city_len) { + ret = KNOT_EINVAL; + goto cleanup; + } + } + } + + if (ctx->mode == MODE_SUBNET) { + char *slash = strchr(yp->data, '/'); + view->subnet.prefix = atoi(slash + 1); + *slash = '\0'; + inet_pton(AF_INET, yp->data, &view->subnet.addr); + } + + } + + // Next rrset of the current view. + if (yp->event == YP_EKEY1) { + uint16_t rr_type = KNOT_RRTYPE_A; + if (knot_rrtype_from_string(yp->key, &rr_type) != 0) { + knotd_mod_log(mod, LOG_ERR, "invalid RR type in config"); + ret = KNOT_EINVAL; + goto cleanup; + } + + knot_rrset_t *add_rr = NULL; + for (size_t i = 0; i < view->count; i++) { + if (view->rrsets[i].type == rr_type) { + add_rr = &view->rrsets[i]; + break; + } + } + + if (add_rr == NULL) { + if (view->count == view->avail) { + void *alloc_ret = realloc(view->rrsets, + 2 * view->avail * sizeof(knot_rrset_t)); + if (alloc_ret == NULL) { + ret = KNOT_ENOMEM; + goto cleanup; + } + view->rrsets = alloc_ret; + } + add_rr = &view->rrsets[view->count++]; + knot_dname_t *owner_cpy = knot_dname_copy(owner, NULL); + if (owner_cpy == NULL) { + return KNOT_ENOMEM; + } + knot_rrset_init(add_rr, owner_cpy, rr_type, KNOT_CLASS_IN, ctx->ttl); + } + + // Parse record. + char *input_string = sprintf_alloc("@ %s %s\n", yp->key, yp->data); + if (input_string == NULL) { + ret = KNOT_ENOMEM; + goto cleanup; + } + knotd_mod_log(mod, LOG_DEBUG, "%s", input_string); + + if (zs_set_input_string(scanner, input_string, strlen(input_string)) != 0 || + zs_parse_record(scanner) != 0 || + scanner->state != ZS_STATE_DATA) { + free(input_string); + ret = KNOT_EPARSEFAIL; + goto cleanup; + } + free(input_string); + + // Add new rdata to current rrset. + ret = knot_rrset_add_rdata(add_rr, scanner->r_data, scanner->r_data_length, NULL); + if (ret != KNOT_EOK) { + goto cleanup; + } + } + } + + cleanup: + if (ret != KNOT_EOK) { + clear_geo_view(view); + } + free(view); + zs_deinit(scanner); + free(scanner); + yp_deinit(yp); + free(yp); + return ret; +} + +static void clear_geo_trie(trie_t *trie) +{ + trie_it_t *it = trie_it_begin(trie); + while (!trie_it_finished(it)) { + geo_trie_val_t *val = (geo_trie_val_t *) (*trie_it_val(it)); + for (int i = 0; i < val->count; i++) { + clear_geo_view(&val->views[i]); + } + free(val->views); + free(val); + trie_it_next(it); + } + trie_it_free(it); + trie_clear(trie); +} + +void clear_geo_ctx(geoip_ctx_t *ctx) +{ + kdnssec_ctx_deinit(&ctx->kctx); + free_zone_keys(&ctx->keyset); +#if HAVE_MAXMINDDB + MMDB_close(&ctx->db); +#endif + clear_geo_trie(ctx->geo_trie); + trie_free(ctx->geo_trie); +} + +static knotd_in_state_t geoip_process(knotd_in_state_t state, knot_pkt_t *pkt, + knotd_qdata_t *qdata, knotd_mod_t *mod) +{ + assert(pkt && qdata && mod); + + geoip_ctx_t *ctx = (geoip_ctx_t *)knotd_mod_ctx(mod); + + // Geolocate only A or AAAA records. + uint16_t qtype = knot_pkt_qtype(qdata->query); + if (qtype != KNOT_RRTYPE_A && qtype != KNOT_RRTYPE_AAAA) { + return state; + } + + // Check if geolocation is available for given query. + knot_dname_t *qname = knot_pkt_qname(qdata->query); + size_t qname_len = knot_dname_size(qname); + trie_val_t *val = trie_get_try(ctx->geo_trie, (char *)qname, qname_len); + if (val == NULL) { + // Nothing to do in this module. + return state; + } + + geo_trie_val_t *data = *val; + + // Check if EDNS Client Subnet is available. + const struct sockaddr_storage *remote = NULL; + if (knot_edns_client_subnet_get_addr((struct sockaddr_storage *)remote, qdata->ecs) != KNOT_EOK) { + remote = qdata->params->remote; + } + + if (ctx->mode == MODE_SUBNET) { + in_addr_t addr = ((struct sockaddr_in *)remote)->sin_addr.s_addr; + + // Check whether the remote falls into any geo subnet. + for (int i = 0; i < data->count; i++) { + if (addr_in_subnet(addr, data->views[i].subnet)) { + // Update ECS if used. + if (qdata->ecs != NULL) { + qdata->ecs->scope_len = data->views[i].subnet.prefix; + } + + knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, &data->views[i].rrsets[0], 0); + if (ctx->dnssec && knot_pkt_has_dnssec(qdata->query)) { + knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, &data->views[i].rrsigs[0], 0); + } + return KNOTD_IN_STATE_HIT; + } + } + } + + if (ctx->mode == MODE_GEODB) { + // Check whether the remote falls into any geo location. + for (int i = 0; i < data->count; i++) { + uint16_t netmask = 0; + if (addr_in_geo(mod, (const struct sockaddr *)remote, data->views[i].geodata, &netmask)) { + // Update ECS if used. + if (qdata->ecs != NULL) { + qdata->ecs->scope_len = netmask; + } + + knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, &data->views[i].rrsets[0], 0); + if (ctx->dnssec && knot_pkt_has_dnssec(qdata->query)) { + knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, &data->views[i].rrsigs[0], 0); + } + return KNOTD_IN_STATE_HIT; + } + } + } + + // Dump found rrsets for debug reasons. + for (int i = 0; i < data->count; i++) { + size_t dump_size = 1024; + char *txt_dump = malloc(dump_size); + knot_rrset_txt_dump(&data->views[i].rrsets[0], &txt_dump, &dump_size, &KNOT_DUMP_STYLE_DEFAULT); + knotd_mod_log(mod, LOG_DEBUG, "%s", txt_dump); + free(txt_dump); + } + + return state; +} + +int geoip_load(knotd_mod_t *mod) +{ + // Create module context. + geoip_ctx_t *ctx = calloc(1, sizeof(geoip_ctx_t)); + if (ctx == NULL) { + return KNOT_ENOMEM; + } + + knotd_conf_t conf = knotd_conf_mod(mod, MOD_TTL); + ctx->ttl = conf.single.integer; + conf = knotd_conf_mod(mod, MOD_MODE); + ctx->mode = conf.single.option; + + // Initialize the dname trie. + ctx->geo_trie = trie_create(NULL); + + int ret; + + // Initialize geodb if configured. +#if HAVE_MAXMINDDB + if (ctx->mode == MODE_GEODB) { + conf = knotd_conf_mod(mod, MOD_GEODB_FILE); + ret = MMDB_open(conf.single.string, MMDB_MODE_MMAP, &ctx->db); + if (ret != MMDB_SUCCESS) { + knotd_mod_log(mod, LOG_ERR, "failed to open Geo DB"); + return KNOT_EINVAL; + } + } +#endif + + // Is DNSSEC used on this zone? + conf = knotd_conf_zone(mod, C_DNSSEC_SIGNING, knotd_mod_zone(mod)); + ctx->dnssec = conf.single.boolean; + if (ctx->dnssec) { + ret = kdnssec_ctx_init(mod->config, &ctx->kctx, knotd_mod_zone(mod), NULL); + if (ret != KNOT_EOK) { + clear_geo_ctx(ctx); + free(ctx); + return ret; + } + ret = load_zone_keys(&ctx->kctx, &ctx->keyset, false); + if (ret != KNOT_EOK) { + knotd_mod_log(mod, LOG_ERR, "failed to load keys"); + clear_geo_ctx(ctx); + free(ctx); + return ret; + } + } + + // Parse geo configuration file. + ret = geo_conf_yparse(mod, ctx); + if (ret != KNOT_EOK) { + knotd_mod_log(mod, LOG_ERR, "failed to load geo configuration"); + clear_geo_ctx(ctx); + free(ctx); + return ret; + } + + knotd_mod_ctx_set(mod, ctx); + + return knotd_mod_in_hook(mod, KNOTD_STAGE_BEGIN, geoip_process); +} + +void geoip_unload(knotd_mod_t *mod) +{ + geoip_ctx_t *ctx = knotd_mod_ctx(mod); + if (ctx != NULL) { + clear_geo_ctx(ctx); + } + free(ctx); + assert(mod); +} + +KNOTD_MOD_API(geoip, KNOTD_MOD_FLAG_SCOPE_ZONE | KNOTD_MOD_FLAG_OPT_CONF, + geoip_load, geoip_unload, geoip_conf, NULL); diff --git a/src/knot/modules/geoip/geoip.rst b/src/knot/modules/geoip/geoip.rst new file mode 100644 index 0000000000000000000000000000000000000000..0a0353c242360a0eba2c35d486e85c09b5cb945a --- /dev/null +++ b/src/knot/modules/geoip/geoip.rst @@ -0,0 +1,22 @@ +.. _mod-geoip: + +``geoip`` — +========================= + +Example +------- + +Module reference +---------------- + +:: + + mod-geoip: + +.. _mod-geoip_id: + +id +.. + +A module identifier. +