summaryrefslogtreecommitdiffstats
path: root/src/knot/modules/probe
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/modules/probe')
-rw-r--r--src/knot/modules/probe/Makefile.inc12
-rw-r--r--src/knot/modules/probe/probe.c190
-rw-r--r--src/knot/modules/probe/probe.rst89
3 files changed, 291 insertions, 0 deletions
diff --git a/src/knot/modules/probe/Makefile.inc b/src/knot/modules/probe/Makefile.inc
new file mode 100644
index 0000000..db14fc4
--- /dev/null
+++ b/src/knot/modules/probe/Makefile.inc
@@ -0,0 +1,12 @@
+knot_modules_probe_la_SOURCES = knot/modules/probe/probe.c
+EXTRA_DIST += knot/modules/probe/probe.rst
+
+if STATIC_MODULE_probe
+libknotd_la_SOURCES += $(knot_modules_probe_la_SOURCES)
+endif
+
+if SHARED_MODULE_probe
+knot_modules_probe_la_LDFLAGS = $(KNOTD_MOD_LDFLAGS)
+knot_modules_probe_la_CPPFLAGS = $(KNOTD_MOD_CPPFLAGS)
+pkglib_LTLIBRARIES += knot/modules/probe.la
+endif
diff --git a/src/knot/modules/probe/probe.c b/src/knot/modules/probe/probe.c
new file mode 100644
index 0000000..bcaa707
--- /dev/null
+++ b/src/knot/modules/probe/probe.c
@@ -0,0 +1,190 @@
+/* Copyright (C) 2022 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 <stdio.h>
+#include <stdint.h>
+
+#include "knot/conf/schema.h"
+#include "knot/include/module.h"
+#include "contrib/string.h"
+#include "contrib/time.h"
+#include "libknot/libknot.h"
+
+#ifdef HAVE_ATOMIC
+#define ATOMIC_SET(dst, val) __atomic_store_n(&(dst), (val), __ATOMIC_RELAXED)
+#define ATOMIC_GET(src) __atomic_load_n(&(src), __ATOMIC_RELAXED)
+#else
+#define ATOMIC_SET(dst, val) ((dst) = (val))
+#define ATOMIC_GET(src) (src)
+#endif
+
+#define MOD_PATH "\x04""path"
+#define MOD_CHANNELS "\x08""channels"
+#define MOD_MAX_RATE "\x08""max-rate"
+
+const yp_item_t probe_conf[] = {
+ { MOD_PATH, YP_TSTR, YP_VNONE },
+ { MOD_CHANNELS, YP_TINT, YP_VINT = { 1, UINT16_MAX, 1 } },
+ { MOD_MAX_RATE, YP_TINT, YP_VINT = { 0, UINT32_MAX, 100000 } },
+ { NULL }
+};
+
+typedef struct {
+ knot_probe_t **probes;
+ size_t probe_count;
+ uint64_t *last_times;
+ uint64_t min_diff_ns;
+ char *path;
+} probe_ctx_t;
+
+static void free_probe_ctx(probe_ctx_t *ctx)
+{
+ for (int i = 0; ctx->probes != NULL && i < ctx->probe_count; ++i) {
+ knot_probe_free(ctx->probes[i]);
+ }
+ free(ctx->probes);
+ free(ctx->last_times);
+ free(ctx->path);
+ free(ctx);
+}
+
+static knotd_state_t export(knotd_state_t state, knot_pkt_t *pkt,
+ knotd_qdata_t *qdata, knotd_mod_t *mod)
+{
+ assert(pkt && qdata);
+
+ probe_ctx_t *ctx = knotd_mod_ctx(mod);
+ uint16_t idx = qdata->params->thread_id % ctx->probe_count;
+ knot_probe_t *probe = ctx->probes[idx];
+
+ // Check the rate limit if enabled.
+ if (ctx->min_diff_ns > 0) {
+ struct timespec now = time_now();
+ uint64_t now_ns = 1000000000 * now.tv_sec + now.tv_nsec;
+ uint64_t last_ns = ATOMIC_GET(ctx->last_times[idx]);
+ if (now_ns - last_ns < ctx->min_diff_ns) {
+ return state;
+ }
+ ATOMIC_SET(ctx->last_times[idx], now_ns);
+ }
+
+ // Prepare data sources.
+ struct sockaddr_storage buff;
+ const struct sockaddr_storage *local = knotd_qdata_local_addr(qdata, &buff);
+ const struct sockaddr_storage *remote = knotd_qdata_remote_addr(qdata);
+
+ knot_probe_proto_t proto = (knot_probe_proto_t)qdata->params->proto;
+ const knot_pkt_t *reply = (state != KNOTD_STATE_NOOP ? pkt : NULL);
+
+ uint16_t rcode = qdata->rcode;
+ if (qdata->rcode_tsig != KNOT_RCODE_NOERROR) {
+ rcode = qdata->rcode_tsig;
+ }
+
+ // Fill out and export the data structure.
+ knot_probe_data_t d;
+ int ret = knot_probe_data_set(&d, proto, local, remote, qdata->query, reply, rcode);
+ if (ret == KNOT_EOK) {
+ d.tcp_rtt = knotd_qdata_rtt(qdata);
+ if (qdata->query->opt_rr != NULL) {
+ d.reply.ede = qdata->rcode_ede;
+ }
+ (void)knot_probe_produce(probe, &d, 1);
+ }
+
+ return state;
+}
+
+int probe_load(knotd_mod_t *mod)
+{
+ probe_ctx_t *ctx = calloc(1, sizeof(*ctx));
+ if (ctx == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ knotd_conf_t conf = knotd_conf_mod(mod, MOD_CHANNELS);
+ ctx->probe_count = conf.single.integer;
+
+ conf = knotd_conf_mod(mod, MOD_PATH);
+ if (conf.count == 0) {
+ conf = knotd_conf(mod, C_SRV, C_RUNDIR, NULL);
+ }
+ if (conf.single.string[0] != '/') {
+ char *cwd = realpath("./", NULL);
+ ctx->path = sprintf_alloc("%s/%s", cwd, conf.single.string);
+ free(cwd);
+ } else {
+ ctx->path = strdup(conf.single.string);
+ }
+ if (ctx->path == NULL) {
+ free_probe_ctx(ctx);
+ return KNOT_ENOMEM;
+ }
+
+ ctx->probes = calloc(ctx->probe_count, sizeof(knot_probe_t *));
+ if (ctx->probes == NULL) {
+ free_probe_ctx(ctx);
+ return KNOT_ENOMEM;
+ }
+
+ ctx->last_times = calloc(ctx->probe_count, sizeof(uint64_t));
+ if (ctx->last_times == NULL) {
+ free_probe_ctx(ctx);
+ return KNOT_ENOMEM;
+ }
+
+ ctx->min_diff_ns = 0;
+ conf = knotd_conf_mod(mod, MOD_MAX_RATE);
+ if (conf.single.integer > 0) {
+ ctx->min_diff_ns = ctx->probe_count * 1000000000 / conf.single.integer;
+ }
+
+ for (int i = 0; i < ctx->probe_count; i++) {
+ knot_probe_t *probe = knot_probe_alloc();
+ if (probe == NULL) {
+ free_probe_ctx(ctx);
+ return KNOT_ENOMEM;
+ }
+
+ int ret = knot_probe_set_producer(probe, ctx->path, i + 1);
+ switch (ret) {
+ case KNOT_ECONN:
+ knotd_mod_log(mod, LOG_NOTICE, "channel %i not connected", i + 1);
+ case KNOT_EOK:
+ break;
+ default:
+ free_probe_ctx(ctx);
+ return ret;
+ }
+
+ ctx->probes[i] = probe;
+ }
+
+ knotd_mod_ctx_set(mod, ctx);
+
+ return knotd_mod_hook(mod, KNOTD_STAGE_END, export);
+}
+
+void probe_unload(knotd_mod_t *mod)
+{
+ probe_ctx_t *ctx = knotd_mod_ctx(mod);
+ if (ctx != NULL) {
+ free_probe_ctx(ctx);
+ }
+}
+
+KNOTD_MOD_API(probe, KNOTD_MOD_FLAG_SCOPE_ANY | KNOTD_MOD_FLAG_OPT_CONF,
+ probe_load, probe_unload, probe_conf, NULL);
diff --git a/src/knot/modules/probe/probe.rst b/src/knot/modules/probe/probe.rst
new file mode 100644
index 0000000..e3657b9
--- /dev/null
+++ b/src/knot/modules/probe/probe.rst
@@ -0,0 +1,89 @@
+.. _mod-probe:
+
+``probe`` — DNS traffic probe
+=============================
+
+The module allows the server to send simplified information about regular DNS
+traffic through *UNIX* sockets. The exported information consists of data blocks
+where each data block (datagram) describes one query/response pair. The response
+part can be empty. The receiver can be an arbitrary program using *libknot* interface
+(C or Python). In case of high traffic, more channels (sockets) can be configured
+to allow parallel processing.
+
+.. NOTE::
+ A simple `probe client <https://gitlab.nic.cz/knot/knot-dns/-/blob/master/scripts/probe_dump.py>`_ in Python.
+
+Example
+-------
+
+Default module configuration::
+
+ template:
+ - id: default
+ global-module: mod-probe
+
+Per zone probe with 8 channels and maximum 1M logs per second limit::
+
+ mod-probe:
+ - id: custom
+ path: /tmp/knot-probe
+ channels: 8
+ max-rate: 1000000
+
+ zone:
+ - domain: example.com.
+ module: mod-probe/custom
+
+
+Module reference
+----------------
+
+::
+
+ mod-probe:
+ - id: STR
+ path: STR
+ channels: INT
+ max-rate: INT
+
+.. _mod-probe_id:
+
+id
+..
+
+A module identifier.
+
+.. _mod-probe_path:
+
+path
+....
+
+A directory path the UNIX sockets are located.
+
+.. NOTE::
+ It's recommended to use a directory with the execute permission restricted
+ to the intended probe consumer process owner only.
+
+*Default:* :ref:`rundir<server_rundir>`
+
+.. _mod-probe_channels:
+
+channels
+........
+
+Number of channels (UNIX sockets) the traffic is distributed to. In case of
+high DNS traffic which is beeing processed by many UDP/XDP/TCP workers,
+using more channels reduces the module overhead.
+
+*Default:* ``1``
+
+.. _mod-probe_max-rate:
+
+max-rate
+........
+
+Maximum number of queries/replies per second the probe is allowed to transfer.
+If the limit is exceeded, the over-limit traffic is ignored. Zero value means
+no limit.
+
+*Default:* ``100000`` (one hundred thousand)