summaryrefslogtreecommitdiffstats
path: root/modules/dnstap/dnstap.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/dnstap/dnstap.c')
-rw-r--r--modules/dnstap/dnstap.c382
1 files changed, 382 insertions, 0 deletions
diff --git a/modules/dnstap/dnstap.c b/modules/dnstap/dnstap.c
new file mode 100644
index 0000000..0918426
--- /dev/null
+++ b/modules/dnstap/dnstap.c
@@ -0,0 +1,382 @@
+/*
+ * 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/>.
+ *
+ * @file dnstap.c
+ * @brief dnstap based query logging support
+ *
+ */
+
+#include "lib/module.h"
+#include "lib/layer.h"
+#include "lib/resolve.h"
+#include "modules/dnstap/dnstap.pb-c.h"
+#include <ccan/json/json.h>
+#include <fstrm.h>
+#include "contrib/cleanup.h"
+
+#define DEBUG_MSG(fmt, ...) kr_log_verbose("[dnstap] " fmt, ##__VA_ARGS__);
+#define CFG_SOCK_PATH "socket_path"
+#define CFG_LOG_RESP_PKT "log_responses"
+#define DEFAULT_SOCK_PATH "/tmp/dnstap.sock"
+#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap"
+#define DNSTAP_INITIAL_BUF_SIZE 256
+
+#define auto_destroy_uopts __attribute__((cleanup(fstrm_unix_writer_options_destroy)))
+#define auto_destroy_wopts __attribute__((cleanup(fstrm_writer_options_destroy)))
+
+/* Internal data structure */
+struct dnstap_data {
+ bool log_resp_pkt;
+ struct fstrm_iothr *iothread;
+ struct fstrm_iothr_queue *ioq;
+};
+
+/*
+ * dt_pack packs the dnstap message for transport
+ * https://gitlab.labs.nic.cz/knot/knot-dns/blob/master/src/contrib/dnstap/dnstap.c#L24
+ * */
+uint8_t* dt_pack(const Dnstap__Dnstap *d, uint8_t **buf, size_t *sz)
+{
+ ProtobufCBufferSimple sbuf = { { NULL } };
+
+ sbuf.base.append = protobuf_c_buffer_simple_append;
+ sbuf.len = 0;
+ sbuf.alloced = DNSTAP_INITIAL_BUF_SIZE;
+ sbuf.data = malloc(sbuf.alloced);
+ if (sbuf.data == NULL) {
+ return NULL;
+ }
+ sbuf.must_free_data = true;
+
+ *sz = dnstap__dnstap__pack_to_buffer(d, (ProtobufCBuffer *) &sbuf);
+ *buf = sbuf.data;
+ return *buf;
+}
+
+/* set_address fills in address detail in dnstap_message
+ * https://gitlab.labs.nic.cz/knot/knot-dns/blob/master/src/contrib/dnstap/message.c#L28
+ */
+static void set_address(const struct sockaddr *sockaddr,
+ ProtobufCBinaryData *addr,
+ protobuf_c_boolean *has_addr,
+ uint32_t *port,
+ protobuf_c_boolean *has_port) {
+ const char *saddr = kr_inaddr(sockaddr);
+ if (saddr == NULL) {
+ *has_addr = false;
+ *has_port = false;
+ return;
+ }
+
+ addr->data = (uint8_t *)(saddr);
+ addr->len = kr_inaddr_len(sockaddr);
+ *has_addr = true;
+ *port = kr_inaddr_port(sockaddr);
+ *has_port = true;
+}
+
+/* dnstap_log prepares dnstap message and sent it to fstrm */
+static int dnstap_log(kr_layer_t *ctx) {
+ const struct kr_request *req = ctx->req;
+ const struct kr_module *module = ctx->api->data;
+ const struct kr_rplan *rplan = &req->rplan;
+ const struct dnstap_data *dnstap_dt = module->data;
+
+ /* check if we have a valid iothread */
+ if (!dnstap_dt->iothread || !dnstap_dt->ioq) {
+ DEBUG_MSG("dnstap_dt->iothread or dnstap_dt->ioq is NULL\n");
+ return kr_error(EFAULT);
+ }
+
+ /* current time */
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ /* Create dnstap message */
+ Dnstap__Message m;
+
+ memset(&m, 0, sizeof(m));
+
+ m.base.descriptor = &dnstap__message__descriptor;
+ /* Only handling response */
+ m.type = DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE;
+
+ if (req->qsource.addr) {
+ set_address(req->qsource.addr,
+ &m.query_address,
+ &m.has_query_address,
+ &m.query_port,
+ &m.has_query_port);
+ }
+
+ if (req->qsource.dst_addr) {
+ if (req->qsource.flags.tcp) {
+ m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__TCP;
+ } else {
+ m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__UDP;
+ }
+ m.has_socket_protocol = true;
+
+ set_address(req->qsource.dst_addr,
+ &m.response_address,
+ &m.has_response_address,
+ &m.response_port,
+ &m.has_response_port);
+ switch (req->qsource.dst_addr->sa_family) {
+ case AF_INET:
+ m.socket_family = DNSTAP__SOCKET_FAMILY__INET;
+ m.has_socket_family = true;
+ break;
+ case AF_INET6:
+ m.socket_family = DNSTAP__SOCKET_FAMILY__INET6;
+ m.has_socket_family = true;
+ break;
+ }
+ }
+
+ if (dnstap_dt->log_resp_pkt) {
+ const knot_pkt_t *rpkt = req->answer;
+ m.response_message.len = rpkt->size;
+ m.response_message.data = (uint8_t *)rpkt->wire;
+ m.has_response_message = true;
+ }
+
+ /* set query time to the timestamp of the first kr_query
+ * set response time to now
+ */
+ if (rplan->resolved.len > 0) {
+ struct kr_query *first = rplan->resolved.at[0];
+
+ m.query_time_sec = first->timestamp.tv_sec;
+ m.has_query_time_sec = true;
+ m.query_time_nsec = first->timestamp.tv_usec * 1000;
+ m.has_query_time_nsec = true;
+ }
+
+ /* Response time */
+ m.response_time_sec = now.tv_sec;
+ m.has_response_time_sec = true;
+ m.response_time_nsec = now.tv_usec * 1000;
+ m.has_response_time_nsec = true;
+
+ /* Query Zone */
+ if (rplan->resolved.len > 0) {
+ struct kr_query *last = array_tail(rplan->resolved);
+ /* Only add query_zone when not answered from cache */
+ if (!(last->flags.CACHED)) {
+ const knot_dname_t *zone_cut_name = last->zone_cut.name;
+ if (zone_cut_name != NULL) {
+ m.query_zone.data = (uint8_t *)zone_cut_name;
+ m.query_zone.len = knot_dname_size(zone_cut_name);
+ m.has_query_zone = true;
+ }
+ }
+ }
+
+ /* Create a dnstap Message */
+ Dnstap__Dnstap dnstap = DNSTAP__DNSTAP__INIT;
+ dnstap.type = DNSTAP__DNSTAP__TYPE__MESSAGE;
+ dnstap.message = (Dnstap__Message *)&m;
+
+ /* Pack the message */
+ uint8_t *frame = NULL;
+ size_t size = 0;
+ dt_pack(&dnstap, &frame, &size);
+ if (!frame) {
+ return kr_error(ENOMEM);
+ }
+
+ /* Submit a request to send message to fstrm_iothr*/
+ fstrm_res res = fstrm_iothr_submit(dnstap_dt->iothread, dnstap_dt->ioq, frame, size,
+ fstrm_free_wrapper, NULL);
+ if (res != fstrm_res_success) {
+ DEBUG_MSG("Error submitting dnstap message to iothr\n");
+ free(frame);
+ return kr_error(EBUSY);
+ }
+
+ return ctx->state;
+}
+
+KR_EXPORT
+int dnstap_init(struct kr_module *module) {
+ /* allocated memory for internal data */
+ struct dnstap_data *data = malloc(sizeof(*data));
+ if (!data) {
+ return kr_error(ENOMEM);
+ }
+ memset(data, 0, sizeof(*data));
+
+ /* save pointer to internal struct in module for future reference */
+ module->data = data;
+ return kr_ok();
+}
+
+KR_EXPORT
+int dnstap_deinit(struct kr_module *module) {
+ struct dnstap_data *data = module->data;
+ /* Free allocated memory */
+ if (data) {
+ fstrm_iothr_destroy(&data->iothread);
+ DEBUG_MSG("fstrm iothread destroyed\n");
+ free(data);
+ }
+ return kr_ok();
+}
+
+/* dnstap_unix_writer returns a unix fstream writer
+ * https://gitlab.labs.nic.cz/knot/knot-dns/blob/master/src/knot/modules/dnstap.c#L159
+ */
+static struct fstrm_writer* dnstap_unix_writer(const char *path) {
+
+ auto_destroy_uopts struct fstrm_unix_writer_options *opt = fstrm_unix_writer_options_init();
+ if (!opt) {
+ return NULL;
+ }
+ fstrm_unix_writer_options_set_socket_path(opt, path);
+
+ auto_destroy_wopts struct fstrm_writer_options *wopt = fstrm_writer_options_init();
+ if (!wopt) {
+ fstrm_unix_writer_options_destroy(&opt);
+ return NULL;
+ }
+ fstrm_writer_options_add_content_type(wopt, DNSTAP_CONTENT_TYPE,
+ strlen(DNSTAP_CONTENT_TYPE));
+
+ struct fstrm_writer *writer = fstrm_unix_writer_init(opt, wopt);
+ fstrm_unix_writer_options_destroy(&opt);
+ fstrm_writer_options_destroy(&wopt);
+ if (!writer) {
+ return NULL;
+ }
+
+ fstrm_res res = fstrm_writer_open(writer);
+ if (res != fstrm_res_success) {
+ DEBUG_MSG("fstrm_writer_open returned %d\n", res);
+ fstrm_writer_destroy(&writer);
+ return NULL;
+ }
+
+ return writer;
+}
+
+/* find_string
+ * create a new string from json
+ * *var is set to pointer of new string
+ * node must of type JSON_STRING
+ * new string can be at most len bytes
+ */
+static int find_string(const JsonNode *node, char **val, size_t len) {
+ if (!node || !node->key) {
+ return kr_error(EINVAL);
+ }
+ assert(node->tag == JSON_STRING);
+ *val = strndup(node->string_, len);
+ assert(*val != NULL);
+ return kr_ok();
+}
+
+/* find_bool returns bool from json */
+static bool find_bool(const JsonNode *node) {
+ if (!node || !node->key) {
+ return false;
+ }
+ assert(node->tag == JSON_BOOL);
+ return node->bool_;
+}
+
+/* parse config */
+KR_EXPORT
+int dnstap_config(struct kr_module *module, const char *conf) {
+ struct dnstap_data *data = module->data;
+ auto_free char *sock_path = NULL;
+
+ /* Empty conf passed, set default */
+ if (!conf || strlen(conf) < 1) {
+ sock_path = strndup(DEFAULT_SOCK_PATH, PATH_MAX);
+ } else {
+
+ JsonNode *root_node = json_decode(conf);
+ if (!root_node) {
+ DEBUG_MSG("error parsing json\n");
+ return kr_error(EINVAL);
+ }
+
+ JsonNode *node;
+ /* dnstapPath key */
+ node = json_find_member(root_node, CFG_SOCK_PATH);
+ if (!node || find_string(node, &sock_path, PATH_MAX) != kr_ok()) {
+ sock_path = strndup(DEFAULT_SOCK_PATH, PATH_MAX);
+ }
+
+ /* logRespPkt key */
+ node = json_find_member(root_node, CFG_LOG_RESP_PKT);
+ if (node) {
+ data->log_resp_pkt = find_bool(node);
+ } else {
+ data->log_resp_pkt = false;
+ }
+
+ /* clean up json, we don't need it no more */
+ json_delete(root_node);
+ }
+
+ DEBUG_MSG("opening sock file %s\n",sock_path);
+ struct fstrm_writer *writer = dnstap_unix_writer(sock_path);
+ if (!writer) {
+ DEBUG_MSG("can't create unix writer\n");
+ return kr_error(EINVAL);
+ }
+
+ struct fstrm_iothr_options *opt = fstrm_iothr_options_init();
+ if (!opt) {
+ DEBUG_MSG("can't init fstrm options\n");
+ fstrm_writer_destroy(&writer);
+ return kr_error(EINVAL);
+ }
+
+ /* Create the I/O thread. */
+ data->iothread = fstrm_iothr_init(opt, &writer);
+ fstrm_iothr_options_destroy(&opt);
+ if (!data->iothread) {
+ DEBUG_MSG("can't init fstrm_iothr\n");
+ fstrm_writer_destroy(&writer);
+ return kr_error(ENOMEM);
+ }
+
+ /* Get fstrm thread handle
+ * We only have one input queue, hence idx=0
+ */
+ data->ioq = fstrm_iothr_get_input_queue_idx(data->iothread, 0);
+ if (!data->ioq) {
+ fstrm_iothr_destroy(&data->iothread);
+ DEBUG_MSG("can't get fstrm queue\n");
+ return kr_error(EBUSY);
+ }
+
+ return kr_ok();
+}
+
+KR_EXPORT
+const kr_layer_api_t *dnstap_layer(struct kr_module *module) {
+ static kr_layer_api_t _layer = {
+ .finish = &dnstap_log,
+ };
+ /* Store module reference */
+ _layer.data = module;
+ return &_layer;
+}
+
+KR_MODULE_EXPORT(dnstap)
+