summaryrefslogtreecommitdiffstats
path: root/src/knot/modules/geoip/geodb.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/modules/geoip/geodb.c')
-rw-r--r--src/knot/modules/geoip/geodb.c216
1 files changed, 216 insertions, 0 deletions
diff --git a/src/knot/modules/geoip/geodb.c b/src/knot/modules/geoip/geodb.c
new file mode 100644
index 0000000..97b6609
--- /dev/null
+++ b/src/knot/modules/geoip/geodb.c
@@ -0,0 +1,216 @@
+/* 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
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "knot/modules/geoip/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, const char *input)
+{
+ if (path == NULL || input == NULL) {
+ return -1;
+ }
+
+ // Parse optional type of key.
+ path->type = GEODB_KEY_TXT;
+ const char *delim = input;
+ if (input[0] == '(') {
+ delim = strchr(input, ')');
+ if (delim == NULL) {
+ return -1;
+ }
+ input++;
+ char *type = sprintf_alloc("%.*s", (int)(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 = strchr(input, '/');
+ if (delim == NULL) {
+ delim = input + strlen(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;
+ }
+ input = delim + 1;
+ }
+
+ return 0;
+}
+
+int parse_geodb_data(const char *input, void **geodata, uint32_t *geodata_len,
+ uint8_t *geodepth, geodb_path_t *path, uint16_t path_cnt)
+{
+ for (uint16_t i = 0; i < path_cnt; i++) {
+ const char *delim = strchr(input, ';');
+ if (delim == NULL) {
+ delim = input + strlen(input);
+ }
+ uint16_t 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;
+ }
+ geodata_len[i] = sizeof(uint32_t);
+ break;
+ default:
+ assert(0);
+ return -1;
+ }
+ }
+ if (*delim == '\0') {
+ break;
+ }
+ input = delim + 1;
+ }
+
+ return 0;
+}
+
+bool geodb_available(void)
+{
+#if HAVE_MAXMINDDB
+ return true;
+#else
+ return false;
+#endif
+}
+
+geodb_t *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) {
+ free(db);
+ return NULL;
+ }
+ return db;
+#else
+ return NULL;
+#endif
+}
+
+void geodb_close(geodb_t *geodb)
+{
+#if HAVE_MAXMINDDB
+ MMDB_close(geodb);
+#endif
+}
+
+int geodb_query(geodb_t *geodb, geodb_data_t *entries, struct sockaddr *remote,
+ geodb_path_t *paths, uint16_t path_cnt, uint16_t *netmask)
+{
+#if HAVE_MAXMINDDB
+ int mmdb_error = 0;
+ MMDB_lookup_result_s res;
+ res = MMDB_lookup_sockaddr(geodb, remote, &mmdb_error);
+ if (mmdb_error != MMDB_SUCCESS || !res.found_entry) {
+ return -1;
+ }
+
+ // Save netmask.
+ *netmask = res.netmask;
+
+ for (uint16_t i = 0; i < path_cnt; i++) {
+ // Get the value of the next key.
+ mmdb_error = MMDB_aget_value(&res.entry, &entries[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 || !entries[i].has_data) {
+ entries[i].has_data = false;
+ continue;
+ }
+ // Check the type.
+ if (entries[i].type != type_map[paths[i].type]) {
+ entries[i].has_data = false;
+ continue;
+ }
+ }
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+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 < 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;
+ }
+ }
+ }
+#else
+ return;
+#endif
+}