summaryrefslogtreecommitdiffstats
path: root/modules/nsid
diff options
context:
space:
mode:
Diffstat (limited to 'modules/nsid')
-rw-r--r--modules/nsid/.packaging/test.config4
-rw-r--r--modules/nsid/README.rst35
-rw-r--r--modules/nsid/meson.build25
-rw-r--r--modules/nsid/nsid.c114
-rw-r--r--modules/nsid/nsid.test.lua22
5 files changed, 200 insertions, 0 deletions
diff --git a/modules/nsid/.packaging/test.config b/modules/nsid/.packaging/test.config
new file mode 100644
index 0000000..de54cce
--- /dev/null
+++ b/modules/nsid/.packaging/test.config
@@ -0,0 +1,4 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('nsid')
+assert(nsid)
+quit()
diff --git a/modules/nsid/README.rst b/modules/nsid/README.rst
new file mode 100644
index 0000000..3d0bf4e
--- /dev/null
+++ b/modules/nsid/README.rst
@@ -0,0 +1,35 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _mod-nsid:
+
+Name Server Identifier (NSID)
+=============================
+
+Module ``nsid`` provides server-side support for :rfc:`5001`
+which allows DNS clients to request resolver to send back its NSID
+along with the reply to a DNS request.
+This is useful for debugging larger resolver farms
+(e.g. when using :ref:`systemd-multiple-instances`, anycast or load balancers).
+
+NSID value can be configured in the resolver's configuration file:
+
+.. code-block:: lua
+
+ modules.load('nsid')
+ nsid.name('instance 1')
+
+.. tip:: When dealing with Knot Resolver running in `multiple instances`
+ managed with systemd see :ref:`instance-specific-configuration`.
+
+You can also obtain configured NSID value:
+
+.. code-block:: lua
+
+ > nsid.name()
+ 'instance 1'
+
+The module can be disabled at run-time:
+
+.. code-block:: lua
+
+ modules.unload('nsid')
diff --git a/modules/nsid/meson.build b/modules/nsid/meson.build
new file mode 100644
index 0000000..354e70b
--- /dev/null
+++ b/modules/nsid/meson.build
@@ -0,0 +1,25 @@
+# C module: nsid
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+nsid_src = files([
+ 'nsid.c',
+])
+c_src_lint += nsid_src
+
+nsid_mod = shared_module(
+ 'nsid',
+ nsid_src,
+ dependencies: [
+ libknot,
+ luajit,
+ ],
+ include_directories: mod_inc_dir,
+ name_prefix: '',
+ install: true,
+ install_dir: modules_dir,
+ link_with: kresd,
+)
+
+config_tests += [
+ ['nsid', files('nsid.test.lua')],
+]
diff --git a/modules/nsid/nsid.c b/modules/nsid/nsid.c
new file mode 100644
index 0000000..a67dcdb
--- /dev/null
+++ b/modules/nsid/nsid.c
@@ -0,0 +1,114 @@
+/* Copyright (C) Knot Resolver contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * This module provides NSID support according to RFC 5001. */
+
+#include <libknot/packet/pkt.h>
+#include <contrib/cleanup.h>
+#include <ccan/json/json.h>
+#include <lauxlib.h>
+
+#include "daemon/engine.h"
+#include "lib/layer.h"
+
+struct nsid_config {
+ uint8_t *local_nsid;
+ size_t local_nsid_len;
+};
+
+static int nsid_finalize(kr_layer_t *ctx) {
+ const struct kr_module *module = ctx->api->data;
+ const struct nsid_config *config = module->data;
+ struct kr_request *req = ctx->req;
+
+ /* no local NSID configured, do nothing */
+ if (config->local_nsid == NULL)
+ return ctx->state;
+
+ const knot_rrset_t *src_opt = req->qsource.packet->opt_rr;
+ /* no EDNS in request, do nothing */
+ if (src_opt == NULL)
+ return ctx->state;
+
+ const uint8_t *req_nsid = knot_edns_get_option(src_opt, KNOT_EDNS_OPTION_NSID, NULL);
+ /* NSID option must be explicitly requested */
+ if (req_nsid == NULL)
+ return ctx->state;
+
+ /* Check violation of https://tools.ietf.org/html/rfc5001#section-2.1:
+ * The resolver MUST NOT include any NSID payload data in the query */
+ if (knot_edns_opt_get_length(req_nsid) != 0)
+ kr_log_debug(NSID, "[%05u. ] FORMERR: NSID option in query "
+ "must not contain payload, continuing\n", req->uid);
+ /* FIXME: actually change RCODE in answer to FORMERR? */
+
+ /* Sanity check, answer should have EDNS as well but who knows ... */
+ if (kr_fails_assert(req->answer->opt_rr))
+ return ctx->state;
+
+ if (knot_edns_add_option(req->answer->opt_rr, KNOT_EDNS_OPTION_NSID,
+ config->local_nsid_len, config->local_nsid,
+ &req->pool) != KNOT_EOK) {
+ /* something went wrong and there is no way to salvage content of OPT RRset */
+ kr_log_debug(NSID, "[%05u. ] unable to add NSID option\n", req->uid);
+ knot_rrset_clear(req->answer->opt_rr, &req->pool);
+ }
+
+ return ctx->state;
+}
+
+static char* nsid_name(void *env, struct kr_module *module, const char *args)
+{
+ struct engine *engine = env;
+ struct nsid_config *config = module->data;
+ if (args) { /* set */
+ /* API is not binary safe, we need to fix this one day */
+ uint8_t *arg_copy = (uint8_t *)strdup(args);
+ if (arg_copy == NULL)
+ luaL_error(engine->L, "[nsid] error while allocating new NSID value\n");
+ free(config->local_nsid);
+ config->local_nsid = arg_copy;
+ config->local_nsid_len = strlen(args);
+ }
+
+ /* get */
+ if (config->local_nsid != NULL)
+ return json_encode_string((char *)config->local_nsid);
+ else
+ return NULL;
+}
+
+KR_EXPORT
+int nsid_init(struct kr_module *module) {
+ static kr_layer_api_t layer = {
+ .answer_finalize = &nsid_finalize,
+ };
+ layer.data = module;
+ module->layer = &layer;
+
+ static const struct kr_prop props[] = {
+ { &nsid_name, "name", "Get or set local NSID value" },
+ { NULL, NULL, NULL }
+ };
+ module->props = props;
+
+ struct nsid_config *config = calloc(1, sizeof(struct nsid_config));
+ if (config == NULL)
+ return kr_error(ENOMEM);
+
+ module->data = config;
+ return kr_ok();
+}
+
+KR_EXPORT
+int nsid_deinit(struct kr_module *module) {
+ struct nsid_config *config = module->data;
+ if (config != NULL) {
+ free(config->local_nsid);
+ free(config);
+ module->data = NULL;
+ }
+ return kr_ok();
+}
+
+KR_MODULE_EXPORT(nsid)
diff --git a/modules/nsid/nsid.test.lua b/modules/nsid/nsid.test.lua
new file mode 100644
index 0000000..8e5d4f9
--- /dev/null
+++ b/modules/nsid/nsid.test.lua
@@ -0,0 +1,22 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- disable networking so we can get SERVFAIL immediately
+net.ipv4 = false
+net.ipv6 = false
+
+-- test for nsid.name() interface
+local function test_nsid_name()
+ if nsid then
+ modules.unload('nsid')
+ end
+ modules.load('nsid')
+ same(nsid.name(), nil, 'NSID modes not provide default NSID value')
+ same(nsid.name('123456'), '123456', 'NSID value can be changed')
+ same(nsid.name(), '123456', 'NSID module remembers configured NSID value')
+ modules.unload('nsid')
+ modules.load('nsid')
+ same(nsid.name(), nil, 'NSID module reload removes configured value')
+end
+
+return {
+ test_nsid_name,
+}