_Bool http : 1;
_Bool xdp : 1;
};
+typedef unsigned long kr_rule_tags_t;
struct kr_extended_error {
int32_t info_code;
const char *extra_text;
unsigned int count_no_nsaddr;
unsigned int count_fail_row;
alloc_wire_f alloc_wire_cb;
+ kr_rule_tags_t rule_tags;
struct kr_extended_error extended_error;
};
enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32};
struct local_state *local_state;
};
typedef int kr_log_level_t;
-enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG};
+enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_RULES, LOG_GRP_REQDBG};
kr_layer_t kr_layer_t_static;
_Bool kr_dbg_assertion_abort;
_Bool http : 1;
_Bool xdp : 1;
};
+typedef unsigned long kr_rule_tags_t;
struct kr_extended_error {
int32_t info_code;
const char *extra_text;
unsigned int count_no_nsaddr;
unsigned int count_fail_row;
alloc_wire_f alloc_wire_cb;
+ kr_rule_tags_t rule_tags;
struct kr_extended_error extended_error;
};
enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32};
struct local_state *local_state;
};
typedef int kr_log_level_t;
-enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG};
+enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_RULES, LOG_GRP_REQDBG};
kr_layer_t kr_layer_t_static;
_Bool kr_dbg_assertion_abort;
_Bool http : 1;
_Bool xdp : 1;
};
+typedef unsigned long kr_rule_tags_t;
+struct kr_rule_zonefile_config {
+ const char *filename;
+ const char *input_str;
+ size_t input_len;
+ _Bool is_rpz;
+ _Bool nodata;
+ kr_rule_tags_t tags;
+ const char *origin;
+ uint32_t ttl;
+};
struct kr_extended_error {
int32_t info_code;
const char *extra_text;
unsigned int count_no_nsaddr;
unsigned int count_fail_row;
alloc_wire_f alloc_wire_cb;
+ kr_rule_tags_t rule_tags;
struct kr_extended_error extended_error;
};
enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32};
struct local_state *local_state;
};
typedef int kr_log_level_t;
-enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG};
+enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_RULES, LOG_GRP_REQDBG};
kr_layer_t kr_layer_t_static;
_Bool kr_dbg_assertion_abort;
kr_qarray_t
struct kr_rplan
struct kr_request_qsource_flags
+ kr_rule_tags_t
struct kr_extended_error
struct kr_request
enum kr_rank
#include "lib/defines.h"
#include "lib/dnssec.h"
#include "lib/log.h"
+#include "lib/resolve.h"
+#include "lib/rules/api.h"
#include <arpa/inet.h>
#include <getopt.h>
goto cleanup;
}
+ ret = kr_rules_init();
+ if (ret) {
+ kr_log_error(RULES, "failed to initialize policy rule engine: %s\n",
+ kr_strerror(ret));
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
for (i = 0; i < the_args->config.len; ++i) {
const char *config = the_args->config.at[i];
if (engine_loadconf(&engine, config) != 0) {
cleanup:/* Cleanup. */
engine_deinit(&engine);
worker_deinit();
+ kr_rules_deinit();
if (loop != NULL) {
uv_loop_close(loop);
}
#include "lib/generic/trie.h"
#include "lib/resolve.h"
#include "lib/rplan.h"
+#include "lib/rules/api.h"
#include "lib/utils.h"
#include "lib/cache/impl.h"
* LMDB only restricts our env without changing the in-file maxsize.
* That is worked around by reopening (found no other reliable way). */
cache->api->close(cache->db, &cache->stats);
- struct kr_cdb_opts opts2;
- memcpy(&opts2, opts, sizeof(opts2));
+ struct kr_cdb_opts opts2 = *opts;
opts2.maxsize = 0;
ret = cache->api->open(&cache->db, &cache->stats, &opts2, mm);
}
}
/* ATM cache only peeks for qry->sname and that would be useless
* to repeat on every iteration, so disable it from now on.
+ * Note that xNAME causes a followup kr_query, so cache will get re-tried.
* LATER(optim.): assist with more precise QNAME minimization. */
qry->flags.CACHE_TRIED = true;
return ctx->state;
}
- int ret = peek_nosync(ctx, pkt);
+ /* TODO: we _might_ want to process rules here even when some of the cache
+ * exit-conditions happen, though I don't expect these cases to be important. */
+ int ret = kr_rule_local_data_answer(qry, pkt);
+ if (ret != -ENOENT) {
+ return ret;
+ }
+
+ ret = peek_nosync(ctx, pkt);
kr_cache_commit(&req->ctx->cache);
return ret;
}
int kr_cache_clear(struct kr_cache *cache);
-/* ** This interface is temporary. ** */
+/* ** This interface is temporary. **
+ * _peek_exact() doesn't look e.g. at signed wildcards
+ * or at local data defined in rules
+ * */
struct kr_cache_p {
uint32_t time; /**< The time of inception. */
#pragma once
+#include <stdbool.h>
#include <stdint.h>
#include <libknot/db/db.h>
struct kr_cdb_opts {
const char *path; /*!< Cache URI path. */
size_t maxsize; /*!< Suggested cache size in bytes; pass 0 to keep unchanged/default. */
+ bool is_cache; /*!< Some behavior changes based on use case. TODO: details. */
};
struct kr_cdb_stats {
MDB_cursor *ro_curs;
} txn;
+ bool is_cache; /**< cache vs. rules; from struct kr_cdb_opts::is_cache */
+
/* Cached part of struct stat for data.mdb. */
dev_t st_dev;
ino_t st_ino;
if (ret != MDB_SUCCESS) goto error_mdb;
}
- /* Cache doesn't require durability, we can be
- * loose with the requirements as a tradeoff for speed. */
- const unsigned flags = MDB_WRITEMAP | MDB_MAPASYNC | MDB_NOTLS;
+ const unsigned flags = env->is_cache
+ /* Cache doesn't require durability, we can be
+ * loose with the requirements as a tradeoff for speed. */
+ ? MDB_WRITEMAP | MDB_NOTLS | MDB_MAPASYNC
+ : MDB_WRITEMAP | MDB_NOTLS;
ret = mdb_env_open(env->env, path, flags, LMDB_FILE_MODE);
if (ret != MDB_SUCCESS) goto error_mdb;
ret = mdb_txn_begin(env->env, NULL, 0, &txn);
if (ret != MDB_SUCCESS) goto error_mdb;
- ret = mdb_dbi_open(txn, NULL, 0, &env->dbi);
+
+ //FIXME: perhaps we want MDB_DUPSORT in future,
+ // but for that we'd have to avoid MDB_RESERVE.
+ // (including a proper assertion, instead of sometimes-crash inside lmdb)
+ const unsigned dbi_flags = 0; //is_cache ? 0 : MDB_DUPSORT;
+ ret = mdb_dbi_open(txn, NULL, dbi_flags, &env->dbi);
if (ret != MDB_SUCCESS) {
mdb_txn_abort(txn);
goto error_mdb;
}
#if !defined(__MACOSX__) && !(defined(__APPLE__) && defined(__MACH__))
- if (size_requested) {
+ if (size_requested && env->is_cache) { // prealloc makes no sense for rules
ret = posix_fallocate(fd, 0, MAX(env->mapsize, env->st_size));
} else {
ret = 0;
if (!env) {
return kr_error(ENOMEM);
}
+ env->is_cache = opts->is_cache;
+
int ret = cdb_open_env(env, opts->path, opts->maxsize, stats);
if (ret != 0) {
free(env);
static int cdb_clear(kr_cdb_pt db, struct kr_cdb_stats *stats)
{
+ //TODO: adjust logging based on env->is_cache
struct lmdb_env *env = db2env(db);
stats->clear++;
/* First try mdb_drop() to clear the DB; this may fail with ENOSPC. */
cdb_readv, cdb_writev, cdb_remove,
cdb_match,
cdb_read_leq,
- cdb_usage_percent,
- cdb_get_maxsize,
+ cdb_usage_percent, cdb_get_maxsize,
cdb_check_health,
};
-
return &api;
}
#include "lib/cache/cdb_api.h"
#include "lib/defines.h"
+/** Get API implementation for LMDB. */
KR_EXPORT KR_CONST
const struct kr_cdb_api *kr_cdb_lmdb(void);
(void)data; // silence analyzers
}
-/** Materialize a knot_rdataset_t from cache with given TTL.
- * Return the number of bytes consumed or an error code.
- */
-static int rdataset_materialize(knot_rdataset_t * restrict rds, const uint8_t * const data,
+int rdataset_materialize(knot_rdataset_t * restrict rds, const uint8_t * const data,
const uint8_t *data_bound, knot_mm_t *pool)
{
if (kr_fails_assert(rds && data && data_bound && data_bound > data && !rds->rdata
/** Serialize an rdataset. It may be NULL as short-hand for empty. */
void rdataset_dematerialize(const knot_rdataset_t *rds, uint8_t * restrict data);
+/** Materialize a knot_rdataset_t from cache.
+ * Return the number of bytes consumed or an error code. */
+int rdataset_materialize(knot_rdataset_t * restrict rds, const uint8_t * const data,
+ const uint8_t *data_bound, knot_mm_t *pool);
+
/** Partially constructed answer when gathering RRsets from cache. */
struct answer {
GRP_NAME_ITEM(LOG_GRP_DEVEL),
GRP_NAME_ITEM(LOG_GRP_RENUMBER),
GRP_NAME_ITEM(LOG_GRP_EDE),
+ GRP_NAME_ITEM(LOG_GRP_RULES),
GRP_NAME_ITEM(LOG_GRP_REQDBG),
{ NULL, LOG_GRP_UNKNOWN },
};
LOG_GRP_DEVEL,
LOG_GRP_RENUMBER,
LOG_GRP_EDE,
+ LOG_GRP_RULES,
/* ^^ Add new log groups above ^^. */
LOG_GRP_REQDBG, /* Must be first non-displayed entry in enum! */
};
#define LOG_GRP_DEVEL_TAG "devel" /**< ``devel``: for development purposes */
#define LOG_GRP_RENUMBER_TAG "renum" /**< ``renum``: operation related to renumber */
#define LOG_GRP_EDE_TAG "exterr" /**< ``exterr``: extended error module */
+#define LOG_GRP_RULES_TAG "rules" /**< ``rules``: new policy rules (their processing) */
#define LOG_GRP_REQDBG_TAG "reqdbg" /**< ``reqdbg``: debug logs enabled by policy actions */
///@}
'layer/iterate.c',
'layer/validate.c',
'log.c',
+ 'rules/api.c',
+ 'rules/defaults.c',
'module.c',
'resolve.c',
'rplan.c',
'module.h',
'resolve.h',
'rplan.h',
+ 'rules/api.h',
+ 'rules/impl.h',
'selection.h',
'selection_forward.h',
'selection_iter.h',
#include "lib/rplan.h"
#include "lib/module.h"
#include "lib/cache/api.h"
+#include "lib/rules/api.h"
/**
* @file resolve.h
unsigned int count_no_nsaddr;
unsigned int count_fail_row;
alloc_wire_f alloc_wire_cb; /**< CB to allocate answer wire (can be NULL). */
+ kr_rule_tags_t rule_tags; /**< TagSet applying to this request. */
struct kr_extended_error extended_error; /**< EDE info; don't modify directly, use kr_request_set_extended_error() */
};
--- /dev/null
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "lib/rules/api.h"
+#include "lib/rules/impl.h"
+
+#include "lib/cache/cdb_lmdb.h"
+
+#include <stdlib.h>
+
+#include "lib/cache/impl.h"
+#undef VERBOSE_MSG
+#define VERBOSE_MSG(qry, ...) kr_log_q((qry), RULES, ## __VA_ARGS__)
+
+struct kr_rules {
+ /* Database for storing the rules (LMDB). */
+ kr_cdb_pt db; /**< Storage instance */
+ const struct kr_cdb_api *api; /**< Storage engine */
+ struct kr_cdb_stats stats;
+};
+
+struct kr_rules *the_rules = NULL;
+#define ruledb_op(op, ...) \
+ the_rules->api->op(the_rules->db, &the_rules->stats, ## __VA_ARGS__)
+
+/* DB key-space summary
+
+ - "\0" starts special keys like "\0rulesets" or "\0stamp"
+ - some future additions?
+ - otherwise it's rulesets - each has a prefix, e.g. RULESET_DEFAULT,
+ its length is bounded by KEY_RULESET_MAXLEN - 1; after that prefix:
+ - KEY_EXACT_MATCH + dname_lf ended by double '\0' + KNOT_RRTYPE_FOO
+ -> exact-match rule (for the given name)
+ - KEY_ZONELIKE_A + dname_lf (no '\0' at end)
+ -> zone-like apex (on the given name)
+ */
+
+#define KEY_RULESET_MAXLEN 16 /**< max. len of ruleset ID + 1(for kind) */
+static /*const*/ char RULESET_DEFAULT[] = "d";
+
+static const uint8_t KEY_EXACT_MATCH[1] = "e";
+static const uint8_t KEY_ZONELIKE_A [1] = "a";
+
+/** The first byte of zone-like apex value is its type. */
+typedef uint8_t val_zla_type_t;
+enum {
+ /** Empty zone. No data in DB value after this byte. */
+ VAL_ZLAT_EMPTY = 1,
+};
+
+
+static int answer_exact_match(struct kr_query *qry, knot_pkt_t *pkt, uint16_t type,
+ const uint8_t *data, const uint8_t *data_bound);
+static int answer_zla_empty(struct kr_query *qry, knot_pkt_t *pkt,
+ knot_db_val_t zla_lf, knot_db_val_t val);
+
+//TODO later, maybe. ATM it would be cumbersome to avoid void* arithmetics.
+#pragma GCC diagnostic ignored "-Wpointer-arith"
+
+int kr_rules_init(void)
+{
+ kr_require(!the_rules);
+ the_rules = calloc(1, sizeof(*the_rules));
+ kr_require(the_rules);
+ the_rules->api = kr_cdb_lmdb();
+
+ struct kr_cdb_opts opts = {
+ .is_cache = false,
+ .path = "ruledb", // under current workdir
+ // FIXME: the file will be sparse, but we still need to choose its size somehow.
+ // Later we might improve it to auto-resize in case of running out of space.
+ // Caveat: mdb_env_set_mapsize() can only be called without transactions open.
+ .maxsize = 10 * 1024*(size_t)1024,
+ };
+ int ret = the_rules->api->open(&the_rules->db, &the_rules->stats, &opts, NULL);
+ /* No persistence - we always refill from config for now.
+ * LATER:
+ * - Make it include versioning?
+ * - "\0stamp" key when loading config(s)?
+ * - Don't clear ruleset data that doesn't come directly from config;
+ * and add marks for that, etc.
+ * (after there actually are any kinds of rules like that)
+ */
+ if (ret == 0) ret = ruledb_op(clear);
+ if (ret != 0) goto failure;
+ kr_require(the_rules->db);
+
+ ret = rules_defaults_insert();
+ if (ret != 0) goto failure;
+
+ /* Activate one default ruleset. */
+ uint8_t key_rs[] = "\0rulesets";
+ knot_db_val_t key = { .data = key_rs, .len = sizeof(key_rs) };
+ knot_db_val_t rulesets = { .data = &RULESET_DEFAULT, .len = strlen(RULESET_DEFAULT) + 1 };
+ ret = ruledb_op(write, &key, &rulesets, 1);
+ if (ret == 0) ret = ruledb_op(commit);
+ if (ret == 0) return kr_ok();
+failure:
+ free(the_rules);
+ the_rules = NULL;
+ return ret;
+}
+
+void kr_rules_deinit(void)
+{
+ if (!the_rules) return;
+ ruledb_op(close);
+ free(the_rules);
+ the_rules = NULL;
+}
+
+static bool kr_rule_consume_tags(knot_db_val_t *val, const struct kr_request *req)
+{
+ const size_t tl = sizeof(kr_rule_tags_t);
+ if (kr_fails_assert(val->len >= tl)) {
+ val->len = 0;
+ /* We may not fail immediately, but further processing
+ * will fail anyway due to zero remaining length. */
+ return false;
+ }
+ kr_rule_tags_t tags;
+ memcpy(&tags, val->data, tl);
+ val->data += tl;
+ val->len -= tl;
+ return tags == KR_RULE_TAGS_ALL || (tags & req->rule_tags);
+}
+
+
+
+
+
+
+/** When constructing a key, it's convenient that the dname_lf ends on a fixed offset.
+ * Convention: the end here is before the final '\0' byte (if any). */
+#define KEY_DNAME_END_OFFSET (KEY_RULESET_MAXLEN + KNOT_DNAME_MAXLEN)
+#define KEY_MAXLEN (KEY_DNAME_END_OFFSET + 64) //TODO: most of 64 is unused ATM
+
+/** Add name lookup format on the fixed end-position inside key_data.
+ *
+ * Note: key_data[KEY_DNAME_END_OFFSET] = '\0' even though
+ * not always used as a part of the key. */
+static inline uint8_t * key_dname_lf(const knot_dname_t *name, uint8_t *key_data)
+{
+ return knot_dname_lf(name, key_data + KEY_RULESET_MAXLEN + 1)
+ + 1/*drop length*/;
+}
+
+/** Return length of the common prefix of two strings (knot_db_val_t). */
+static size_t key_common_prefix(knot_db_val_t k1, knot_db_val_t k2)
+{
+ const size_t len = MIN(k1.len, k2.len);
+ const uint8_t *data1 = k1.data, *data2 = k2.data;
+ kr_require(len == 0 || (data1 && data2));
+ for (ssize_t i = 0; i < len; ++i) {
+ if (data1[i] != data2[i])
+ return i;
+ }
+ return len;
+}
+
+/** Find common "subtree" of two strings that both end in a dname_lf ('\0' terminator excluded).
+ *
+ * Note: return value < lf_start can happen - mismatch happened before LF.
+ * Function reviewed thoroughly, including the dependency.
+ */
+static size_t key_common_subtree(knot_db_val_t k1, knot_db_val_t k2, size_t lf_start_i)
+{
+ ssize_t i = key_common_prefix(k1, k2);
+ const char *data1 = k1.data, *data2 = k2.data;
+ // beware: '\0' at the end is excluded, so we need to handle ends separately
+ if (i == 0
+ || (i == k1.len && i == k2.len)
+ || (i == k1.len && data2[i] == '\0')
+ || (i == k2.len && data1[i] == '\0')) {
+ return i;
+ }
+ do {
+ --i;
+ if (i < lf_start_i)
+ return i;
+ if (data2[i] == '\0')
+ return i;
+ } while (true);
+}
+
+int kr_rule_local_data_answer(struct kr_query *qry, knot_pkt_t *pkt)
+{
+ // TODO: implement EDE codes somehow
+
+ const uint16_t rrtype = qry->stype;
+
+ // LATER(optim.): we might cache the ruleset list a bit
+ uint8_t key_rs[] = "\0rulesets";
+ knot_db_val_t rulesets = { NULL, 0 };
+ int ret;
+ {
+ knot_db_val_t key = { .data = key_rs, .len = sizeof(key_rs) };
+ ret = ruledb_op(read, &key, &rulesets, 1);
+ }
+ if (ret != 0) return ret; /* including ENOENT: no rulesets -> no rule used */
+ const char *rulesets_str = rulesets.data;
+
+ uint8_t key_data[KEY_MAXLEN];
+ knot_db_val_t key;
+ key.data = key_dname_lf(qry->sname, key_data);
+ key_data[KEY_DNAME_END_OFFSET + 1] = '\0'; // double zero
+
+ key.data -= sizeof(KEY_EXACT_MATCH);
+ uint8_t * const key_data_ruleset_end = key.data;
+
+ /* Iterate over all rulesets. */
+ while (rulesets.len > 0) {
+ { /* Write ruleset-specific prefix of the key. */
+ const size_t rsp_len = strnlen(rulesets_str, rulesets.len);
+ kr_require(rsp_len <= KEY_RULESET_MAXLEN - 1);
+ key.data -= rsp_len;
+ memcpy(key.data, rulesets_str, rsp_len);
+ rulesets_str += rsp_len + 1;
+ rulesets.len -= rsp_len + 1;
+ }
+
+ /* Probe for exact and CNAME rule. */
+ memcpy(key_data_ruleset_end, &KEY_EXACT_MATCH, sizeof(KEY_EXACT_MATCH));
+ key.len = key_data + KEY_DNAME_END_OFFSET + 2 + sizeof(rrtype)
+ - (uint8_t *)key.data;
+ const uint16_t types[] = { rrtype, KNOT_RRTYPE_CNAME };
+ const bool want_CNAME = rrtype != KNOT_RRTYPE_CNAME
+ && rrtype != KNOT_RRTYPE_DS;
+ for (int i = 0; i < 1 + want_CNAME; ++i) {
+ memcpy(key_data + KEY_DNAME_END_OFFSET + 2, &types[i], sizeof(rrtype));
+ knot_db_val_t val;
+ // LATER: use cursor to iterate over multiple rules on the same key,
+ // testing tags on each
+ ret = ruledb_op(read, &key, &val, 1);
+ switch (ret) {
+ case -ENOENT: continue;
+ case 0: break;
+ default: return ret;
+ }
+ if (!kr_rule_consume_tags(&val, qry->request)) continue;
+
+ /* We found a rule that applies to the dname+rrtype+req. */
+ return answer_exact_match(qry, pkt, types[i],
+ val.data, val.data + val.len);
+ }
+
+ /* Find the closest zone-like apex that applies.
+ * Now the key needs one byte change and a little truncation
+ * (we may truncate repeatedly). */
+ static_assert(sizeof(KEY_ZONELIKE_A) == sizeof(KEY_EXACT_MATCH),
+ "bad combination of constants");
+ memcpy(key_data_ruleset_end, &KEY_ZONELIKE_A, sizeof(KEY_ZONELIKE_A));
+ key.len = key_data + KEY_DNAME_END_OFFSET - (uint8_t *)key.data;
+ const size_t lf_start_i = key_data_ruleset_end + sizeof(KEY_ZONELIKE_A)
+ - (const uint8_t *)key.data;
+ kr_require(lf_start_i < KEY_MAXLEN);
+ knot_db_val_t key_leq = key;
+ knot_db_val_t val;
+ if (rrtype == KNOT_RRTYPE_DS)
+ goto shorten; // parent-side type, belongs into zone closer to root
+ // LATER: again, use cursor to iterate over multiple rules on the same key.
+ do {
+ ret = ruledb_op(read_leq, &key_leq, &val);
+ if (ret == -ENOENT) break;
+ if (ret < 0) return kr_error(ret);
+ if (ret > 0) { // found a previous key
+ size_t cs_len = key_common_subtree(key, key_leq, lf_start_i);
+ if (cs_len < lf_start_i) // no suitable key can exist in DB
+ break;
+ if (cs_len < key_leq.len) { // retry at the common subtree
+ key_leq.len = cs_len;
+ continue;
+ }
+ kr_assert(cs_len == key_leq.len);
+ }
+ const knot_db_val_t zla_lf = {
+ .data = key_leq.data + lf_start_i,
+ .len = key_leq.len - lf_start_i,
+ };
+ /* Found some good key, now check tags. */
+ if (!kr_rule_consume_tags(&val, qry->request)) {
+ kr_assert(key_leq.len >= lf_start_i);
+ shorten:
+ /* Shorten key_leq by one label and retry. */
+ if (key_leq.len <= lf_start_i) // nowhere to shorten
+ break;
+ const char *data = key_leq.data;
+ while (key_leq.len > lf_start_i && data[--key_leq.len] != '\0') ;
+ continue;
+ }
+ /* Tags OK; execute the rule. */
+ val_zla_type_t ztype;
+ if (val.len < sizeof(ztype))
+ return kr_error(EILSEQ);
+ memcpy(&ztype, val.data, sizeof(ztype));
+ ++val.data; --val.len;
+ switch (ztype) {
+ case VAL_ZLAT_EMPTY:
+ return answer_zla_empty(qry, pkt, zla_lf, val);
+ default:
+ return kr_error(EILSEQ);
+ }
+ } while (true);
+ }
+
+ return kr_error(ENOENT);
+}
+
+#define CHECK_RET(ret) do { \
+ if ((ret) < 0) { kr_assert(false); return kr_error((ret)); } \
+} while (false)
+
+static int answer_exact_match(struct kr_query *qry, knot_pkt_t *pkt, uint16_t type,
+ const uint8_t *data, const uint8_t *data_bound)
+{
+ /* Extract ttl from data. */
+ uint32_t ttl;
+ if (kr_fails_assert(data + sizeof(ttl) <= data_bound))
+ return kr_error(EILSEQ);
+ memcpy(&ttl, data, sizeof(ttl));
+ data += sizeof(ttl);
+
+ /* Start constructing the (pseudo-)packet. */
+ int ret = pkt_renew(pkt, qry->sname, qry->stype);
+ CHECK_RET(ret);
+ struct answer_rrset arrset;
+ memset(&arrset, 0, sizeof(arrset));
+
+ /* Materialize the base RRset.
+ * Error handling: we assume it's OK to leak a bit memory from pkt->mm. */
+ arrset.set.rr = knot_rrset_new(qry->sname, type, KNOT_CLASS_IN, ttl, &pkt->mm);
+ if (kr_fails_assert(arrset.set.rr))
+ return kr_error(ENOMEM);
+ ret = rdataset_materialize(&arrset.set.rr->rrs, data, data_bound, &pkt->mm);
+ CHECK_RET(ret);
+ const size_t data_off = ret;
+ arrset.set.rank = KR_RANK_SECURE | KR_RANK_AUTH; // local data has high trust
+ arrset.set.expiring = false;
+ /* Materialize the RRSIG RRset for the answer in (pseudo-)packet.
+ * (There will almost never be any RRSIG.) */
+ ret = rdataset_materialize(&arrset.sig_rds, data + data_off, data_bound, &pkt->mm);
+ CHECK_RET(ret);
+
+ /* Sanity check: we consumed exactly all data. */
+ const int unused_bytes = data_bound - data - data_off - ret;
+ if (kr_fails_assert(unused_bytes == 0)) {
+ kr_log_error(RULES, "ERROR: unused bytes: %d\n", unused_bytes);
+ return kr_error(EILSEQ);
+ }
+
+ /* Put links to the materialized data into the pkt. */
+ ret = pkt_append(pkt, &arrset);
+ CHECK_RET(ret);
+
+ /* Finishing touches. */
+ qry->flags.EXPIRING = false;
+ qry->flags.CACHED = true;
+ qry->flags.NO_MINIMIZE = true;
+
+ VERBOSE_MSG(qry, "=> satisfied by local data (positive)\n");
+ return kr_ok();
+}
+
+int kr_rule_local_data_ins(const knot_rrset_t *rrs, const knot_rdataset_t *sig_rds,
+ kr_rule_tags_t tags)
+{
+ uint8_t key_data[KEY_MAXLEN];
+ knot_db_val_t key;
+ key.data = key_dname_lf(rrs->owner, key_data);
+ key_data[KEY_DNAME_END_OFFSET + 1] = '\0'; // double zero
+
+ key.data -= sizeof(KEY_EXACT_MATCH);
+ memcpy(key.data, &KEY_EXACT_MATCH, sizeof(KEY_EXACT_MATCH));
+
+ const size_t rsp_len = strlen(RULESET_DEFAULT);
+ key.data -= rsp_len;
+ memcpy(key.data, RULESET_DEFAULT, rsp_len);
+
+ memcpy(key_data + KEY_DNAME_END_OFFSET + 2, &rrs->type, sizeof(rrs->type));
+ key.len = key_data + KEY_DNAME_END_OFFSET + 2 + sizeof(rrs->type)
+ - (uint8_t *)key.data;
+
+ const int rr_ssize = rdataset_dematerialize_size(&rrs->rrs);
+ const int to_alloc = sizeof(tags) + sizeof(rrs->ttl) + rr_ssize
+ + rdataset_dematerialize_size(sig_rds);
+ knot_db_val_t val = { .data = NULL, .len = to_alloc };
+ int ret = ruledb_op(write, &key, &val, 1);
+ CHECK_RET(ret);
+
+ memcpy(val.data, &tags, sizeof(tags));
+ val.data += sizeof(tags);
+ memcpy(val.data, &rrs->ttl, sizeof(rrs->ttl));
+ val.data += sizeof(rrs->ttl);
+ rdataset_dematerialize(&rrs->rrs, val.data);
+ val.data += rr_ssize;
+ rdataset_dematerialize(sig_rds, val.data);
+
+ return kr_ok();
+}
+
+
+static int answer_zla_empty(struct kr_query *qry, knot_pkt_t *pkt,
+ const knot_db_val_t zla_lf, const knot_db_val_t val)
+{
+ if (kr_fails_assert(val.len == 0)) {
+ kr_log_error(RULES, "ERROR: unused bytes: %zu\n", val.len);
+ return kr_error(EILSEQ);
+ }
+
+ knot_dname_t apex_name[KNOT_DNAME_MAXLEN];
+ int ret = knot_dname_lf2wire(apex_name, zla_lf.len, zla_lf.data);
+ CHECK_RET(ret);
+
+ /* Start constructing the (pseudo-)packet. */
+ ret = pkt_renew(pkt, qry->sname, qry->stype);
+ CHECK_RET(ret);
+ struct answer_rrset arrset;
+ memset(&arrset, 0, sizeof(arrset));
+
+ /* Construct SOA or NS data (hardcoded content). The SOA content is
+ * as recommended except for using a fixed mname (for simplicity):
+ https://tools.ietf.org/html/rfc6303#section-3
+ */
+ static const uint8_t soa_rdata[] = "\x09localhost\0\6nobody\7invalid\0"
+ "\0\0\0\1\0\0\x0e\x10\0\0\4\xb0\0\x09\x3a\x80\0\0\x2a\x30";
+ const bool name_matches = knot_dname_is_equal(qry->sname, apex_name);
+ const bool want_NS = name_matches && qry->stype == KNOT_RRTYPE_NS;
+ arrset.set.rr = knot_rrset_new(apex_name, want_NS ? KNOT_RRTYPE_NS : KNOT_RRTYPE_SOA,
+ KNOT_CLASS_IN, RULE_TTL_DEFAULT, &pkt->mm);
+ if (kr_fails_assert(arrset.set.rr))
+ return kr_error(ENOMEM);
+ if (want_NS) {
+ kr_require(zla_lf.len + 2 == knot_dname_size(apex_name));
+ ret = knot_rrset_add_rdata(arrset.set.rr, apex_name, zla_lf.len + 2, &pkt->mm);
+ } else {
+ ret = knot_rrset_add_rdata(arrset.set.rr, soa_rdata,
+ sizeof(soa_rdata) - 1, &pkt->mm);
+ }
+ CHECK_RET(ret);
+ arrset.set.rank = KR_RANK_SECURE | KR_RANK_AUTH; // local data has high trust
+ arrset.set.expiring = false;
+
+ /* Small differences if we exactly hit the name or even type. */
+ if (name_matches) {
+ knot_wire_set_rcode(pkt->wire, KNOT_RCODE_NOERROR);
+ } else {
+ knot_wire_set_rcode(pkt->wire, KNOT_RCODE_NXDOMAIN);
+ }
+ if (want_NS || (name_matches && qry->stype == KNOT_RRTYPE_SOA)) {
+ ret = knot_pkt_begin(pkt, KNOT_ANSWER);
+ } else {
+ ret = knot_pkt_begin(pkt, KNOT_AUTHORITY);
+ }
+ CHECK_RET(ret);
+
+ /* Put links to the RR into the pkt. */
+ ret = pkt_append(pkt, &arrset);
+ CHECK_RET(ret);
+
+ /* Finishing touches. */
+ qry->flags.EXPIRING = false;
+ qry->flags.CACHED = true;
+ qry->flags.NO_MINIMIZE = true;
+
+ VERBOSE_MSG(qry, "=> satisfied by local data (empty zone)\n");
+ return kr_ok();
+}
+
+int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags)
+{
+ uint8_t key_data[KEY_MAXLEN];
+ knot_db_val_t key;
+ key.data = key_dname_lf(apex, key_data);
+
+ key.data -= sizeof(KEY_ZONELIKE_A);
+ memcpy(key.data, &KEY_ZONELIKE_A, sizeof(KEY_ZONELIKE_A));
+
+ const size_t rsp_len = strlen(RULESET_DEFAULT);
+ key.data -= rsp_len;
+ memcpy(key.data, RULESET_DEFAULT, rsp_len);
+ key.len = key_data + KEY_DNAME_END_OFFSET - (uint8_t *)key.data;
+
+ val_zla_type_t ztype = VAL_ZLAT_EMPTY;
+ knot_db_val_t val = {
+ .data = NULL,
+ .len = sizeof(tags) + sizeof(ztype),
+ };
+ int ret = ruledb_op(write, &key, &val, 1);
+ CHECK_RET(ret);
+ memcpy(val.data, &tags, sizeof(tags));
+ val.data += sizeof(tags);
+ memcpy(val.data, &ztype, sizeof(ztype));
+ val.data += sizeof(ztype);
+ return ret;
+}
+
--- /dev/null
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+#pragma once
+
+#include "lib/defines.h"
+struct kr_query;
+struct knot_pkt;
+
+typedef uint64_t kr_rule_tags_t;
+#define KR_RULE_TAGS_ALL ((kr_rule_tags_t)0)
+
+KR_EXPORT
+int kr_rules_init(void);
+
+KR_EXPORT
+void kr_rules_deinit(void);
+
+/** Try answering the query from local data.
+ *
+ * FIXME: we probably want to ensure AA flags in answer as appropriate.
+ * Perhaps approach it like AD? Tweak flags in ranked_rr_array_entry
+ * and at the end decide whether to set AA=1?
+ */
+int kr_rule_local_data_answer(struct kr_query *qry, struct knot_pkt *pkt);
+
+/** Insert/overwrite a local data rule.
+ * Into the default rule-set ATM. */
+KR_EXPORT
+int kr_rule_local_data_ins(const knot_rrset_t *rrs, const knot_rdataset_t *sig_rds,
+ kr_rule_tags_t tags);
+
+/** Insert an empty zone.
+ * Into the default rule-set ATM. */
+KR_EXPORT
+int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags);
+
--- /dev/null
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "lib/rules/impl.h"
+#include "lib/rules/api.h"
+#include "lib/utils.h"
+
+int rules_defaults_insert(void)
+{
+ static const char * names[] = {
+ /* RFC1918 Private, local, broadcast, test and special zones
+ Considerations: RFC6761, sec 6.1.
+ https://www.iana.org/assignments/locally-served-dns-zones
+ */
+ /* RFC6303 */
+ "10.in-addr.arpa.",
+ "16.172.in-addr.arpa.",
+ "17.172.in-addr.arpa.",
+ "18.172.in-addr.arpa.",
+ "19.172.in-addr.arpa.",
+ "20.172.in-addr.arpa.",
+ "21.172.in-addr.arpa.",
+ "22.172.in-addr.arpa.",
+ "23.172.in-addr.arpa.",
+ "24.172.in-addr.arpa.",
+ "25.172.in-addr.arpa.",
+ "26.172.in-addr.arpa.",
+ "27.172.in-addr.arpa.",
+ "28.172.in-addr.arpa.",
+ "29.172.in-addr.arpa.",
+ "30.172.in-addr.arpa.",
+ "31.172.in-addr.arpa.",
+ "168.192.in-addr.arpa.",
+ "0.in-addr.arpa.",
+ "127.in-addr.arpa.",
+ "254.169.in-addr.arpa.",
+ "2.0.192.in-addr.arpa.",
+ "100.51.198.in-addr.arpa.",
+ "113.0.203.in-addr.arpa.",
+ "255.255.255.255.in-addr.arpa.",
+ /* RFC7793 */
+ "64.100.in-addr.arpa.",
+ "65.100.in-addr.arpa.",
+ "66.100.in-addr.arpa.",
+ "67.100.in-addr.arpa.",
+ "68.100.in-addr.arpa.",
+ "69.100.in-addr.arpa.",
+ "70.100.in-addr.arpa.",
+ "71.100.in-addr.arpa.",
+ "72.100.in-addr.arpa.",
+ "73.100.in-addr.arpa.",
+ "74.100.in-addr.arpa.",
+ "75.100.in-addr.arpa.",
+ "76.100.in-addr.arpa.",
+ "77.100.in-addr.arpa.",
+ "78.100.in-addr.arpa.",
+ "79.100.in-addr.arpa.",
+ "80.100.in-addr.arpa.",
+ "81.100.in-addr.arpa.",
+ "82.100.in-addr.arpa.",
+ "83.100.in-addr.arpa.",
+ "84.100.in-addr.arpa.",
+ "85.100.in-addr.arpa.",
+ "86.100.in-addr.arpa.",
+ "87.100.in-addr.arpa.",
+ "88.100.in-addr.arpa.",
+ "89.100.in-addr.arpa.",
+ "90.100.in-addr.arpa.",
+ "91.100.in-addr.arpa.",
+ "92.100.in-addr.arpa.",
+ "93.100.in-addr.arpa.",
+ "94.100.in-addr.arpa.",
+ "95.100.in-addr.arpa.",
+ "96.100.in-addr.arpa.",
+ "97.100.in-addr.arpa.",
+ "98.100.in-addr.arpa.",
+ "99.100.in-addr.arpa.",
+ "100.100.in-addr.arpa.",
+ "101.100.in-addr.arpa.",
+ "102.100.in-addr.arpa.",
+ "103.100.in-addr.arpa.",
+ "104.100.in-addr.arpa.",
+ "105.100.in-addr.arpa.",
+ "106.100.in-addr.arpa.",
+ "107.100.in-addr.arpa.",
+ "108.100.in-addr.arpa.",
+ "109.100.in-addr.arpa.",
+ "110.100.in-addr.arpa.",
+ "111.100.in-addr.arpa.",
+ "112.100.in-addr.arpa.",
+ "113.100.in-addr.arpa.",
+ "114.100.in-addr.arpa.",
+ "115.100.in-addr.arpa.",
+ "116.100.in-addr.arpa.",
+ "117.100.in-addr.arpa.",
+ "118.100.in-addr.arpa.",
+ "119.100.in-addr.arpa.",
+ "120.100.in-addr.arpa.",
+ "121.100.in-addr.arpa.",
+ "122.100.in-addr.arpa.",
+ "123.100.in-addr.arpa.",
+ "124.100.in-addr.arpa.",
+ "125.100.in-addr.arpa.",
+ "126.100.in-addr.arpa.",
+ "127.100.in-addr.arpa.",
+ /* RFC6303 */
+ "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.",
+ "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.",
+ /* ^ below we inject exact-match PTR over this empty zone */
+ "d.f.ip6.arpa.",
+ "8.e.f.ip6.arpa.",
+ "9.e.f.ip6.arpa.",
+ "a.e.f.ip6.arpa.",
+ "b.e.f.ip6.arpa.",
+ "8.b.d.0.1.0.0.2.ip6.arpa.",
+ /* RFC8375 */
+ "home.arpa.",
+
+ /* More zones - empty-zone subset from:
+ https://www.iana.org/assignments/special-use-domain-names
+ TODO: perhaps review the list again.
+ */
+ "test.",
+ "onion.",
+ "invalid.",
+ "local.", // RFC 8375.4
+ };
+
+ const int names_count = sizeof(names) / sizeof(names[0]);
+ for (int i = 0; i < names_count; ++i) {
+ knot_dname_t name_buf[KNOT_DNAME_MAXLEN];
+ const knot_dname_t *dname =
+ knot_dname_from_str(name_buf, names[i], sizeof(name_buf));
+ int ret = kr_rule_local_data_emptyzone(dname, KR_RULE_TAGS_ALL);
+ if (kr_fails_assert(!ret))
+ return kr_error(ret);
+ /* The double conversion is perhaps a bit wasteful, but it should be rare. */
+ /* LATER: add extra info with explanation? policy module had an ADDITIONAL
+ * record with explanation, but perhaps extended errors are more suitable?
+ * Differentiating the message - perhaps splitting VAL_ZLAT_EMPTY into a few?
+ */
+ }
+
+ { // reverse localhost; LATER: the situation isn't ideal with NXDOMAIN + some exact matches
+ knot_dname_t name_buf[KNOT_DNAME_MAXLEN];
+ knot_rrset_t rr = {
+ .owner = knot_dname_from_str(name_buf,
+ "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.",
+ sizeof(name_buf)),
+ .ttl = RULE_TTL_DEFAULT,
+ .type = KNOT_RRTYPE_PTR,
+ .rclass = KNOT_CLASS_IN,
+ .rrs = { 0 },
+ .additional = NULL,
+ };
+ int ret = knot_rrset_add_rdata(&rr, (const knot_dname_t *)"\x09localhost\0",
+ 1+9+1, NULL);
+ if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+
+ rr.owner = knot_dname_from_str(name_buf,
+ "1.0.0.127.in-addr.arpa.",
+ sizeof(name_buf));
+ if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+
+ knot_rdataset_clear(&rr.rrs, NULL);
+ if (kr_fails_assert(!ret))
+ return kr_error(ret);
+ }
+
+ return kr_ok();
+}
+
--- /dev/null
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+#pragma once
+
+#define RULE_TTL_DEFAULT ((uint16_t)10800)
+
+/** Insert all the default rules. in ./defaults.c */
+int rules_defaults_insert(void);
+
#include "lib/zonecut.h"
#include "lib/module.h"
#include "lib/layer.h"
+#include "lib/rules/api.h"
#include <inttypes.h>
#include <math.h>
kr_inaddr_family(&ia.ip));
}
+static int add_pair_root(struct kr_zonecut *hints, const char *name, const char *addr)
+{
+ /* Build key */
+ knot_dname_t key[KNOT_DNAME_MAXLEN];
+ if (!knot_dname_from_str(key, name, sizeof(key))) {
+ return kr_error(EINVAL);
+ }
+ knot_dname_to_lower(key);
+
+ union kr_sockaddr ia;
+ if (parse_addr_str(&ia, addr) != 0) {
+ return kr_error(EINVAL);
+ }
+ return kr_zonecut_add(hints, key, kr_inaddr(&ia.ip), kr_inaddr_len(&ia.ip));
+}
+
static int add_pair(struct kr_zonecut *hints, const char *name, const char *addr)
{
/* Build key */
return kr_error(EINVAL);
}
- return kr_zonecut_add(hints, key, kr_inaddr(&ia.ip), kr_inaddr_len(&ia.ip));
+ uint16_t rrtype = ia.ip.sa_family == AF_INET6 ? KNOT_RRTYPE_AAAA : KNOT_RRTYPE_A;
+ knot_rrset_t rrs;
+ knot_rrset_init(&rrs, key, rrtype, KNOT_CLASS_IN, HINTS_TTL_DEFAULT/*FIXME*/);
+ int ret;
+ if (ia.ip.sa_family == AF_INET6) {
+ ret = knot_rrset_add_rdata(&rrs, (const uint8_t *)&ia.ip6.sin6_addr, 16, NULL);
+ } else {
+ ret = knot_rrset_add_rdata(&rrs, (const uint8_t *)&ia.ip4.sin_addr, 4, NULL);
+ }
+ if (ret == KNOT_EOK) {
+ ret = kr_rule_local_data_ins(&rrs, NULL, KR_RULE_TAGS_ALL);
+ }
+ knot_rdataset_clear(&rrs.rrs, NULL);
+ return ret;
}
static int add_reverse_pair(struct kr_zonecut *hints, const char *name, const char *addr)
JsonNode *node = NULL;
json_foreach(node, table) {
switch(node->tag) {
- case JSON_STRING: add_pair(root_hints, name ? name : node->key, node->string_); break;
- case JSON_ARRAY: unpack_hint(root_hints, node, name ? name : node->key); break;
+ case JSON_STRING:
+ add_pair_root(root_hints, name ? name : node->key, node->string_);
+ break;
+ case JSON_ARRAY:
+ unpack_hint(root_hints, node, name ? name : node->key);
+ break;
default: continue;
}
}
return kres.DONE
end
-local dname_rev4_localhost = todname('1.0.0.127.in-addr.arpa');
-local dname_rev4_localhost_apex = todname('127.in-addr.arpa');
-
--- Rule for reverse localhost.
--- Answer with locally served minimal 127.in-addr.arpa domain, only having
--- a PTR record in 1.0.0.127.in-addr.arpa, and with 1.0...0.ip6.arpa. zone.
--- TODO: much of this would better be left to the hints module (or coordinated).
-local function localhost_reversed(_, req)
- local qry = req:current()
- local answer = req:ensure_answer()
- if answer == nil then return nil end
-
- -- classify qry.sname:
- local is_exact -- exact dname for localhost
- local is_apex -- apex of a locally-served localhost zone
- local is_nonterm -- empty non-terminal name
- if ffi.C.knot_dname_in_bailiwick(qry.sname, todname('ip6.arpa.')) > 0 then
- -- exact ::1 query (relying on the calling rule)
- is_exact = true
- is_apex = true
- else
- -- within 127.in-addr.arpa.
- local labels = ffi.C.knot_dname_labels(qry.sname, nil)
- if labels == 3 then
- is_exact = false
- is_apex = true
- elseif labels == 4+2 and ffi.C.knot_dname_is_equal(
- qry.sname, dname_rev4_localhost) then
- is_exact = true
- else
- is_exact = false
- is_apex = false
- is_nonterm = ffi.C.knot_dname_in_bailiwick(dname_rev4_localhost, qry.sname) > 0
- end
- end
-
- ffi.C.kr_pkt_make_auth_header(answer)
- answer:rcode(kres.rcode.NOERROR)
- answer:begin(kres.section.ANSWER)
- if is_exact and qry.stype == kres.type.PTR then
- answer:put(qry.sname, 900, answer:qclass(), kres.type.PTR, dname_localhost)
- elseif is_apex and qry.stype == kres.type.SOA then
- mkauth_soa(answer, dname_rev4_localhost_apex, dname_localhost)
- elseif is_apex and qry.stype == kres.type.NS then
- answer:put(dname_rev4_localhost_apex, 900, answer:qclass(), kres.type.NS,
- dname_localhost)
- else
- if not is_nonterm then
- answer:rcode(kres.rcode.NXDOMAIN)
- end
- answer:begin(kres.section.AUTHORITY)
- mkauth_soa(answer, dname_rev4_localhost_apex, dname_localhost)
- end
- return kres.DONE
-end
-
-- All requests
function policy.all(action)
return function(_, _) return action end
return names
end
--- RFC1918 Private, local, broadcast, test and special zones
--- Considerations: RFC6761, sec 6.1.
--- https://www.iana.org/assignments/locally-served-dns-zones
-local private_zones = {
- -- RFC6303
- '10.in-addr.arpa.',
- '16.172.in-addr.arpa.',
- '17.172.in-addr.arpa.',
- '18.172.in-addr.arpa.',
- '19.172.in-addr.arpa.',
- '20.172.in-addr.arpa.',
- '21.172.in-addr.arpa.',
- '22.172.in-addr.arpa.',
- '23.172.in-addr.arpa.',
- '24.172.in-addr.arpa.',
- '25.172.in-addr.arpa.',
- '26.172.in-addr.arpa.',
- '27.172.in-addr.arpa.',
- '28.172.in-addr.arpa.',
- '29.172.in-addr.arpa.',
- '30.172.in-addr.arpa.',
- '31.172.in-addr.arpa.',
- '168.192.in-addr.arpa.',
- '0.in-addr.arpa.',
- '254.169.in-addr.arpa.',
- '2.0.192.in-addr.arpa.',
- '100.51.198.in-addr.arpa.',
- '113.0.203.in-addr.arpa.',
- '255.255.255.255.in-addr.arpa.',
- -- RFC7793
- '64.100.in-addr.arpa.',
- '65.100.in-addr.arpa.',
- '66.100.in-addr.arpa.',
- '67.100.in-addr.arpa.',
- '68.100.in-addr.arpa.',
- '69.100.in-addr.arpa.',
- '70.100.in-addr.arpa.',
- '71.100.in-addr.arpa.',
- '72.100.in-addr.arpa.',
- '73.100.in-addr.arpa.',
- '74.100.in-addr.arpa.',
- '75.100.in-addr.arpa.',
- '76.100.in-addr.arpa.',
- '77.100.in-addr.arpa.',
- '78.100.in-addr.arpa.',
- '79.100.in-addr.arpa.',
- '80.100.in-addr.arpa.',
- '81.100.in-addr.arpa.',
- '82.100.in-addr.arpa.',
- '83.100.in-addr.arpa.',
- '84.100.in-addr.arpa.',
- '85.100.in-addr.arpa.',
- '86.100.in-addr.arpa.',
- '87.100.in-addr.arpa.',
- '88.100.in-addr.arpa.',
- '89.100.in-addr.arpa.',
- '90.100.in-addr.arpa.',
- '91.100.in-addr.arpa.',
- '92.100.in-addr.arpa.',
- '93.100.in-addr.arpa.',
- '94.100.in-addr.arpa.',
- '95.100.in-addr.arpa.',
- '96.100.in-addr.arpa.',
- '97.100.in-addr.arpa.',
- '98.100.in-addr.arpa.',
- '99.100.in-addr.arpa.',
- '100.100.in-addr.arpa.',
- '101.100.in-addr.arpa.',
- '102.100.in-addr.arpa.',
- '103.100.in-addr.arpa.',
- '104.100.in-addr.arpa.',
- '105.100.in-addr.arpa.',
- '106.100.in-addr.arpa.',
- '107.100.in-addr.arpa.',
- '108.100.in-addr.arpa.',
- '109.100.in-addr.arpa.',
- '110.100.in-addr.arpa.',
- '111.100.in-addr.arpa.',
- '112.100.in-addr.arpa.',
- '113.100.in-addr.arpa.',
- '114.100.in-addr.arpa.',
- '115.100.in-addr.arpa.',
- '116.100.in-addr.arpa.',
- '117.100.in-addr.arpa.',
- '118.100.in-addr.arpa.',
- '119.100.in-addr.arpa.',
- '120.100.in-addr.arpa.',
- '121.100.in-addr.arpa.',
- '122.100.in-addr.arpa.',
- '123.100.in-addr.arpa.',
- '124.100.in-addr.arpa.',
- '125.100.in-addr.arpa.',
- '126.100.in-addr.arpa.',
- '127.100.in-addr.arpa.',
-
- -- RFC6303
- -- localhost_reversed handles ::1
- '0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.',
- 'd.f.ip6.arpa.',
- '8.e.f.ip6.arpa.',
- '9.e.f.ip6.arpa.',
- 'a.e.f.ip6.arpa.',
- 'b.e.f.ip6.arpa.',
- '8.b.d.0.1.0.0.2.ip6.arpa.',
- -- RFC8375
- 'home.arpa.',
-}
-policy.todnames(private_zones)
-
-- @var Default rules
policy.rules = {}
policy.postrules = {}
policy.special_names = {
-- XXX: beware of special_names_optim() when modifying these filters
- {
- cb=policy.suffix_common(policy.DENY_MSG(
- 'Blocking is mandated by standards, see references on '
- .. 'https://www.iana.org/assignments/'
- .. 'locally-served-dns-zones/locally-served-dns-zones.xhtml',
- kres.extended_error.NOTSUP),
- private_zones, todname('arpa.')),
- count=0
- },
- {
- cb=policy.suffix(policy.DENY_MSG(
- 'Blocking is mandated by standards, see references on '
- .. 'https://www.iana.org/assignments/'
- .. 'special-use-domain-names/special-use-domain-names.xhtml',
- kres.extended_error.NOTSUP),
- {
- todname('test.'),
- todname('onion.'),
- todname('invalid.'),
- todname('local.'), -- RFC 8375.4
- }),
- count=0
- },
{
cb=policy.suffix(localhost, {dname_localhost}),
count=0
},
- {
- cb=policy.suffix_common(localhost_reversed, {
- todname('127.in-addr.arpa.'),
- todname('1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.')},
- todname('arpa.')),
- count=0
- },
}
-- Return boolean; false = no special name may apply, true = some might apply.
local result = check_stats(got)
return result(state, req)
end
+policy.add(policy.all(policy.FLAGS('PASSTHRU_LEGACY'))) -- the test isn't written with this in mind
policy.add(policy.pattern(reply_result, 'stats.test.'))
policy.add(policy.all(FWD_TARGET)) -- avoid iteration
return -ENOENT;
}
- struct kr_cdb_opts opts = { .path = cache_path, .maxsize = 0/*don't resize*/ };
+ struct kr_cdb_opts opts = {
+ .is_cache = true,
+ .path = cache_path,
+ .maxsize = 0,/*don't resize*/
+ };
int ret = kr_cache_open(kres_db, NULL, &opts, NULL);
if (ret || kres_db->db == NULL) {