Commit 63f46e50 authored by Mark Karpilovskij's avatar Mark Karpilovskij

geoip: finalize config format, support for geoname_id

parent 3e529a2f
......@@ -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/geodb.c
src/knot/modules/geoip/geodb.h
src/knot/modules/geoip/geoip.c
src/knot/modules/noudp/noudp.c
......
knot_modules_geoip_la_SOURCES = knot/modules/geoip/geoip.c
EXTRA_DIST += knot/modules/geoip/geoip.rst
knot_modules_geoip_la_SOURCES = knot/modules/geoip/geoip.c \
knot/modules/geoip/geodb.c \
knot/modules/geoip/geodb.h
EXTRA_DIST += knot/modules/geoip/geoip.rst
if STATIC_MODULE_geoip
libknotd_la_SOURCES += $(knot_modules_geoip_la_SOURCES)
......
/* 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 "geodb.h"
#include "contrib/strtonum.h"
#include "contrib/string.h"
#if HAVE_MAXMINDDB
static const uint16_t type_map[] = {
[GEODB_KEY_ID] = MMDB_DATA_TYPE_UINT32,
[GEODB_KEY_TXT] = MMDB_DATA_TYPE_UTF8_STRING
};
#endif
int parse_geodb_path(geodb_path_t *path, char *input)
{
if (path == NULL || input == NULL) {
return -1;
}
// Parse optional type of key.
path->type = GEODB_KEY_TXT;
char *delim = input;
if (input[0] == '(') {
delim = strchrnul(input, ')');
if (*delim != ')') {
return -1;
}
input++;
char *type = sprintf_alloc("%.*s", delim - input, input);
const knot_lookup_t *table = knot_lookup_by_name(geodb_key_types, type);
free(type);
if (table == NULL) {
return -1;
}
path->type = table->id;
input = delim + 1;
}
// Parse the path.
uint16_t len = 0;
while (1) {
delim = strchrnul(input, '/');
path->path[len] = malloc(delim - input + 1);
if (path->path[len] == NULL) {
return -1;
}
memcpy(path->path[len], input, delim - input);
path->path[len][delim - input] = '\0';
len++;
if (*delim == 0 || len == GEODB_MAX_PATH_LEN) {
break;
} else {
input = delim + 1;
}
}
if (len == 0) {
return -1;
}
return 0;
}
int parse_geodata(char *input, void **geodata, uint32_t *geodata_len,
uint8_t *geodepth, geodb_path_t *path, uint16_t path_cnt)
{
char *delim = NULL;
uint16_t key_len = 0;
for (uint16_t i = 0; i < path_cnt; i++) {
delim = strchrnul(input, ';');
key_len = delim - input;
if (key_len > 0 && !(key_len == 1 && *input == '*')) {
*geodepth = i + 1;
switch (path[i].type) {
case GEODB_KEY_TXT:
geodata[i] = malloc(key_len + 1);
if (geodata[i] == NULL) {
return -1;
}
memcpy(geodata[i], input, key_len);
((char *)geodata[i])[key_len] = '\0';
geodata_len[i] = key_len;
break;
case GEODB_KEY_ID:
geodata[i] = malloc(sizeof(uint32_t));
if (geodata[i] == NULL) {
return -1;
}
if (str_to_u32(input, (uint32_t *)geodata[i]) != KNOT_EOK) {
return -1;
}
}
}
if (*delim == '\0') {
break;
} else {
input = delim + 1;
}
}
return 0;
}
void *geodb_open(const char *filename)
{
#if HAVE_MAXMINDDB
MMDB_s *db = calloc(1, sizeof(MMDB_s));
if (db == NULL) {
return NULL;
}
int mmdb_error = MMDB_open(filename, MMDB_MODE_MMAP, db);
if (mmdb_error != MMDB_SUCCESS) {
return NULL;
}
return (void *)db;
#endif
return NULL;
}
void *geodb_alloc_entries(uint16_t count)
{
#if HAVE_MAXMINDDB
MMDB_entry_data_s *entries = calloc(count, sizeof(MMDB_entry_data_s));
return (void *)entries;
#endif
}
void geodb_close(void *geodb)
{
#if HAVE_MAXMINDDB
MMDB_s *db = (MMDB_s *)geodb;
MMDB_close(db);
#endif
}
int geodb_query(void *geodb, void *entries, struct sockaddr *remote,
geodb_path_t *paths, uint16_t path_cnt, uint16_t *netmask)
{
#if HAVE_MAXMINDDB
MMDB_s *db = (MMDB_s *)geodb;
int mmdb_error = 0;
MMDB_lookup_result_s res;
res = MMDB_lookup_sockaddr(db, remote, &mmdb_error);
if (mmdb_error != MMDB_SUCCESS || !res.found_entry) {
return -1;
}
// Save netmask.
*netmask = res.netmask;
MMDB_entry_data_s *entry = (MMDB_entry_data_s *)entries;
for (uint16_t i = 0; i < path_cnt; i++) {
// Get the value of the next key.
mmdb_error = MMDB_aget_value(&res.entry, &entry[i], (const char *const*)paths[i].path);
if (mmdb_error != MMDB_SUCCESS && mmdb_error != MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) {
return -1;
}
if (mmdb_error == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR || !entry[i].has_data) {
entry[i].has_data = false;
continue;
}
// Check the type.
if (entry[i].type != type_map[paths[i].type]) {
entry[i].has_data = false;
continue;
}
}
return 0;
#endif
return -1;
}
bool remote_in_geo(void **geodata, uint32_t *geodata_len, uint16_t geodepth, void *entries)
{
#if HAVE_MAXMINDDB
MMDB_entry_data_s *entry = (MMDB_entry_data_s *)entries;
for (int i = 0; i < geodepth; i++) {
// Nothing to do if current geodata do not specify this key.
if (geodata[i] == NULL) {
continue;
}
if (!entry[i].has_data) {
return false;
}
switch (entry[i].type) {
case MMDB_DATA_TYPE_UTF8_STRING:
if (geodata_len[i] != entry[i].data_size ||
memcmp(geodata[i], entry[i].utf8_string, geodata_len[i]) != 0) {
return false;
}
break;
case MMDB_DATA_TYPE_UINT32:
if (*((uint32_t *)geodata[i]) != entry[i].uint32) {
return false;
}
break;
default:
return false;
}
}
return true;
#endif
return false;
}
......@@ -14,91 +14,44 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include<libknot/libknot.h>
#if HAVE_MAXMINDDB
#include <maxminddb.h>
#endif
enum geodb_key {
CONTINENT,
COUNTRY,
CITY,
ISP
};
// MaxMind DB related constants.
#define MAX_PATH_LEN 4
#define GEODB_KEYS 4
#define GEODB_MAX_PATH_LEN 8
#define GEODB_MAX_DEPTH 8
typedef struct {
const char *path[MAX_PATH_LEN];
}mmdb_path_t;
mmdb_path_t paths[] = {
{{"continent", "code", NULL}},
{{"country", "iso_code", NULL}},
{{"city", "names", "en", NULL}},
{{"isp", NULL}}
typedef enum {
GEODB_KEY_ID,
GEODB_KEY_TXT
} geodb_key_type_t;
static const knot_lookup_t geodb_key_types[] = {
{ GEODB_KEY_ID, "id" },
{ GEODB_KEY_TXT, "" }
};
void *geodb_open(const char *filename)
{
#if HAVE_MAXMINDDB
MMDB_s *db = calloc(1, sizeof(MMDB_s));
if (db == NULL) {
return NULL;
}
int mmdb_error = MMDB_open(filename, MMDB_MODE_MMAP, db);
if (mmdb_error != MMDB_SUCCESS) {
return NULL;
}
return (void *)db;
#endif
return NULL;
}
typedef struct {
geodb_key_type_t type;
char *path[GEODB_MAX_PATH_LEN];
}geodb_path_t;
void geodb_close(void *geodb)
{
#if HAVE_MAXMINDDB
MMDB_s *db = (MMDB_s *)geodb;
MMDB_close(db);
#endif
}
int parse_geodb_path(geodb_path_t *path, char *input);
int geodb_query(void *geodb, struct sockaddr *remote,
enum geodb_key *keys, uint16_t keyc,
char **geodata, uint32_t *geodata_len, uint16_t *netmask)
{
#if HAVE_MAXMINDDB
MMDB_s *db = (MMDB_s *)geodb;
int mmdb_error = 0;
MMDB_lookup_result_s res;
res = MMDB_lookup_sockaddr(db, remote, &mmdb_error);
if (mmdb_error != MMDB_SUCCESS || !res.found_entry) {
return -1;
}
// Save netmask.
*netmask = res.netmask;
MMDB_entry_data_s entry;
// Set the remote's geo information.
for (uint16_t i = 0; i < keyc; i++) {
enum geodb_key key = keys[i];
geodata[key] = NULL;
mmdb_error = MMDB_aget_value(&res.entry, &entry, paths[key].path);
if (mmdb_error != MMDB_SUCCESS &&
mmdb_error != MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) {
return -1;
}
if (mmdb_error == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR ||
!entry.has_data || entry.type != MMDB_DATA_TYPE_UTF8_STRING) {
continue;
}
geodata[key] = (char *)entry.utf8_string;
geodata_len[key] = entry.data_size;
}
return 0;
#endif
return -1;
}
int parse_geodata(char *input, void **geodata, uint32_t *geodata_len, uint8_t *geodepth,
geodb_path_t *path, uint16_t path_cnt);
void *geodb_open(const char *filename);
void *geodb_alloc_entries(uint16_t count);
void geodb_close(void *geodb);
int geodb_query(void *geodb, void *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, void *entries);
......@@ -21,7 +21,6 @@
#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"
#include "geodb.h"
......@@ -52,13 +51,6 @@ static const knot_lookup_t modes[] = {
{ 0, NULL }
};
static const knot_lookup_t geodb_keys[] = {
{ CONTINENT, "continent" },
{ COUNTRY, "country" },
{ CITY, "city" },
{ ISP, "isp" }
};
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 } },
......@@ -89,8 +81,6 @@ int geoip_conf_check(knotd_conf_check_args_t *args)
typedef struct {
enum operation_mode mode;
enum geodb_key keys[GEODB_KEYS];
uint16_t keyc;
uint32_t ttl;
trie_t *geo_trie;
......@@ -99,14 +89,17 @@ typedef struct {
kdnssec_ctx_t kctx;
void *geodb;
void *entries;
geodb_path_t paths[GEODB_MAX_DEPTH];
uint16_t path_count;
} geoip_ctx_t;
typedef struct {
struct sockaddr_storage *subnet;
uint8_t subnet_prefix;
char *geodata[GEODB_KEYS];
uint32_t geodata_len[GEODB_KEYS];
void *geodata[GEODB_MAX_DEPTH];
uint32_t geodata_len[GEODB_MAX_DEPTH];
uint8_t geodepth;
size_t count, avail;
......@@ -190,27 +183,9 @@ static bool remote_in_subnet(const struct sockaddr_storage *remote, geo_view_t *
return true;
}
static bool remote_in_geo(geoip_ctx_t *ctx, geo_view_t *view, geo_view_t *remote_view)
{
// Iterate over configured geo keys.
for (uint16_t i = 0; i < ctx->keyc; i++) {
enum geodb_key key = ctx->keys[i];
// Nothing to do if the view does not specify this key.
if (view->geodata[key] == NULL) {
continue;
}
if (remote_view->geodata[key] == NULL ||
view->geodata_len[key] != remote_view->geodata_len[key] ||
memcmp(view->geodata[key], remote_view->geodata[key], view->geodata_len[key]) != 0) {
return false;
}
}
return true;
}
static int finalize_geo_view(geo_view_t *view, knot_dname_t *owner, zone_key_t *key, geoip_ctx_t *ctx)
{
if (view == NULL) {
if (view == NULL || view->count == 0) {
return KNOT_EOK;
}
......@@ -239,8 +214,8 @@ static int finalize_geo_view(geo_view_t *view, knot_dname_t *owner, zone_key_t *
if (ret != KNOT_EOK) {
return ret;
}
view->rrsets = NULL;
view->rrsigs = NULL;
memset(view, 0, sizeof(*view));
return ret;
}
......@@ -265,7 +240,7 @@ static void clear_geo_view(geo_view_t *view)
if (view == NULL) {
return;
}
for (int i = 0; i < GEODB_KEYS; i++) {
for (int i = 0; i < GEODB_MAX_DEPTH; i++) {
free(view->geodata[i]);
}
free(view->subnet);
......@@ -351,7 +326,7 @@ static int geo_conf_yparse(knotd_mod_t *mod, geoip_ctx_t *ctx)
}
char *set_origin = sprintf_alloc("$ORIGIN %s%s\n", yp->key,
(yp->key[yp->key_len-1] == '.') ? "" : ".");
(yp->key[yp->key_len - 1] == '.') ? "" : ".");
if (set_origin == NULL) {
ret = KNOT_ENOMEM;
goto cleanup;
......@@ -370,7 +345,7 @@ static int geo_conf_yparse(knotd_mod_t *mod, geoip_ctx_t *ctx)
// New geo view description starts.
if (yp->event == YP_EID) {
// Initialize new geo view.
memset(view, 0, sizeof(geo_view_t));
memset(view, 0, sizeof(*view));
ret = init_geo_view(view);
if (ret != KNOT_EOK) {
goto cleanup;
......@@ -378,29 +353,11 @@ static int geo_conf_yparse(knotd_mod_t *mod, geoip_ctx_t *ctx)
// Parse geodata/subnet.
if (ctx->mode == MODE_GEODB) {
char *beg = yp->data;
char *end = beg;
uint16_t i = 0;
while (1) {
beg = end;
end = strchrnul(beg, ';');
uint32_t key_len = end - beg;
if (key_len != 0 && !(key_len == 1 && *beg == '*')) {
view->geodepth = i + 1;
enum geodb_key key = ctx->keys[i];
view->geodata[key] = malloc(key_len + 1);
if (view->geodata[key] == NULL) {
ret = KNOT_ENOMEM;
goto cleanup;
}
strlcpy(view->geodata[key], beg, key_len + 1);
view->geodata_len[key] = key_len;
}
if (*end == '\0') {
break;
}
i++;
end++;
if (parse_geodata((char *)yp->data, view->geodata, view->geodata_len,
&view->geodepth, ctx->paths, ctx->path_count) != 0) {
knotd_mod_log(mod, LOG_ERR, "could not parse geoip conf-file");
ret = KNOT_EINVAL;
goto cleanup;
}
}
......@@ -453,7 +410,7 @@ static int geo_conf_yparse(knotd_mod_t *mod, geoip_ctx_t *ctx)
if (add_rr == NULL) {
if (view->count == view->avail) {
void *alloc_ret = realloc(view->rrsets,
2 * view->avail * sizeof(knot_rrset_t));
2 * view->avail * sizeof(knot_rrset_t));
if (alloc_ret == NULL) {
ret = KNOT_ENOMEM;
goto cleanup;
......@@ -530,6 +487,12 @@ void clear_geo_ctx(geoip_ctx_t *ctx)
free(ctx->geodb);
clear_geo_trie(ctx->geo_trie);
trie_free(ctx->geo_trie);
for (int i = 0; i < ctx->path_count; i++) {
for (int j = 0; j < GEODB_MAX_PATH_LEN; j++) {
free(ctx->paths[i].path[j]);
}
}
free(ctx->entries);
}
static knotd_in_state_t geoip_process(knotd_in_state_t state, knot_pkt_t *pkt,
......@@ -586,9 +549,8 @@ static knotd_in_state_t geoip_process(knotd_in_state_t state, knot_pkt_t *pkt,
}
netmask = best_prefix;
} else if (ctx->mode == MODE_GEODB) {
geo_view_t remote_view;
int ret = geodb_query(ctx->geodb, (struct sockaddr *)remote, ctx->keys, ctx->keyc,
remote_view.geodata, remote_view.geodata_len, &netmask);
int ret = geodb_query(ctx->geodb, ctx->entries, (struct sockaddr *)remote,
ctx->paths, ctx->path_count, &netmask);
if (ret != 0) {
return state;
}
......@@ -597,7 +559,8 @@ static knotd_in_state_t geoip_process(knotd_in_state_t state, knot_pkt_t *pkt,
// 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(ctx, view, &remote_view))) {
if (rr == NULL || (view->geodepth > best_depth &&
remote_in_geo(view->geodata, view->geodata_len, view->geodepth, ctx->entries))) {
for (int j = 0; j < view->count; j++) {
if (view->rrsets[j].type == qtype) {
best_depth = view->geodepth;
......@@ -666,11 +629,26 @@ int geoip_load(knotd_mod_t *mod)
// Load configured geodb keys.
conf = knotd_conf_mod(mod, MOD_GEODB_KEY);
ctx->keyc = conf.count;
ctx->path_count = conf.count;
if (ctx->path_count > GEODB_MAX_DEPTH) {
knotd_mod_log(mod, LOG_ERR, "maximal number of geodb-key items (%d) exceeded", GEODB_MAX_DEPTH);
knotd_conf_free(&conf);
return KNOT_EINVAL;
}
for (size_t i = 0; i < conf.count; i++) {
ctx->keys[i] = conf.multi[i].option;
if (parse_geodb_path(&ctx->paths[i], (char *)conf.multi[i].string) != 0) {
knotd_mod_log(mod, LOG_ERR, "unrecognized geodb-key format");
knotd_conf_free(&conf);
return KNOT_EINVAL;
}
}
knotd_conf_free(&conf);
// Allocate space for query entries.
ctx->entries = geodb_alloc_entries(ctx->path_count);
if (ctx->entries == NULL) {
return KNOT_ENOMEM;
}
}
// Is DNSSEC used on this zone?
......
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