diff options
Diffstat (limited to 'modules/dnstap')
25 files changed, 950 insertions, 0 deletions
diff --git a/modules/dnstap/.packaging/centos/7/builddeps b/modules/dnstap/.packaging/centos/7/builddeps new file mode 100644 index 0000000..d3ab354 --- /dev/null +++ b/modules/dnstap/.packaging/centos/7/builddeps @@ -0,0 +1,3 @@ +fstrm-devel +protobuf-c-devel +protobuf-c-compiler diff --git a/modules/dnstap/.packaging/centos/7/rundeps b/modules/dnstap/.packaging/centos/7/rundeps new file mode 100644 index 0000000..06c2792 --- /dev/null +++ b/modules/dnstap/.packaging/centos/7/rundeps @@ -0,0 +1,2 @@ +fstrm +protobuf-c diff --git a/modules/dnstap/.packaging/centos/8/builddeps b/modules/dnstap/.packaging/centos/8/builddeps new file mode 100644 index 0000000..d3ab354 --- /dev/null +++ b/modules/dnstap/.packaging/centos/8/builddeps @@ -0,0 +1,3 @@ +fstrm-devel +protobuf-c-devel +protobuf-c-compiler diff --git a/modules/dnstap/.packaging/centos/8/rundeps b/modules/dnstap/.packaging/centos/8/rundeps new file mode 100644 index 0000000..06c2792 --- /dev/null +++ b/modules/dnstap/.packaging/centos/8/rundeps @@ -0,0 +1,2 @@ +fstrm +protobuf-c diff --git a/modules/dnstap/.packaging/debian/10/builddeps b/modules/dnstap/.packaging/debian/10/builddeps new file mode 100644 index 0000000..417dc04 --- /dev/null +++ b/modules/dnstap/.packaging/debian/10/builddeps @@ -0,0 +1,3 @@ +libfstrm-dev +libprotobuf-c-dev +protobuf-c-compiler diff --git a/modules/dnstap/.packaging/debian/10/rundeps b/modules/dnstap/.packaging/debian/10/rundeps new file mode 100644 index 0000000..a726e12 --- /dev/null +++ b/modules/dnstap/.packaging/debian/10/rundeps @@ -0,0 +1,2 @@ +libfstrm0 +libprotobuf-c1 diff --git a/modules/dnstap/.packaging/debian/9/builddeps b/modules/dnstap/.packaging/debian/9/builddeps new file mode 100644 index 0000000..417dc04 --- /dev/null +++ b/modules/dnstap/.packaging/debian/9/builddeps @@ -0,0 +1,3 @@ +libfstrm-dev +libprotobuf-c-dev +protobuf-c-compiler diff --git a/modules/dnstap/.packaging/debian/9/rundeps b/modules/dnstap/.packaging/debian/9/rundeps new file mode 100644 index 0000000..a726e12 --- /dev/null +++ b/modules/dnstap/.packaging/debian/9/rundeps @@ -0,0 +1,2 @@ +libfstrm0 +libprotobuf-c1 diff --git a/modules/dnstap/.packaging/fedora/31/builddeps b/modules/dnstap/.packaging/fedora/31/builddeps new file mode 100644 index 0000000..d3ab354 --- /dev/null +++ b/modules/dnstap/.packaging/fedora/31/builddeps @@ -0,0 +1,3 @@ +fstrm-devel +protobuf-c-devel +protobuf-c-compiler diff --git a/modules/dnstap/.packaging/fedora/31/rundeps b/modules/dnstap/.packaging/fedora/31/rundeps new file mode 100644 index 0000000..06c2792 --- /dev/null +++ b/modules/dnstap/.packaging/fedora/31/rundeps @@ -0,0 +1,2 @@ +fstrm +protobuf-c diff --git a/modules/dnstap/.packaging/fedora/32/builddeps b/modules/dnstap/.packaging/fedora/32/builddeps new file mode 100644 index 0000000..d3ab354 --- /dev/null +++ b/modules/dnstap/.packaging/fedora/32/builddeps @@ -0,0 +1,3 @@ +fstrm-devel +protobuf-c-devel +protobuf-c-compiler diff --git a/modules/dnstap/.packaging/fedora/32/rundeps b/modules/dnstap/.packaging/fedora/32/rundeps new file mode 100644 index 0000000..06c2792 --- /dev/null +++ b/modules/dnstap/.packaging/fedora/32/rundeps @@ -0,0 +1,2 @@ +fstrm +protobuf-c diff --git a/modules/dnstap/.packaging/leap/15.2/builddeps b/modules/dnstap/.packaging/leap/15.2/builddeps new file mode 100644 index 0000000..30f8d9e --- /dev/null +++ b/modules/dnstap/.packaging/leap/15.2/builddeps @@ -0,0 +1,3 @@ +fstrm-devel +libprotobuf-c-devel +protobuf-c diff --git a/modules/dnstap/.packaging/leap/15.2/rundeps b/modules/dnstap/.packaging/leap/15.2/rundeps new file mode 100644 index 0000000..06c2792 --- /dev/null +++ b/modules/dnstap/.packaging/leap/15.2/rundeps @@ -0,0 +1,2 @@ +fstrm +protobuf-c diff --git a/modules/dnstap/.packaging/test.config b/modules/dnstap/.packaging/test.config new file mode 100644 index 0000000..5966860 --- /dev/null +++ b/modules/dnstap/.packaging/test.config @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('dnstap') +assert(dnstap) +quit() diff --git a/modules/dnstap/.packaging/ubuntu/16.04/builddeps b/modules/dnstap/.packaging/ubuntu/16.04/builddeps new file mode 100644 index 0000000..417dc04 --- /dev/null +++ b/modules/dnstap/.packaging/ubuntu/16.04/builddeps @@ -0,0 +1,3 @@ +libfstrm-dev +libprotobuf-c-dev +protobuf-c-compiler diff --git a/modules/dnstap/.packaging/ubuntu/16.04/rundeps b/modules/dnstap/.packaging/ubuntu/16.04/rundeps new file mode 100644 index 0000000..a726e12 --- /dev/null +++ b/modules/dnstap/.packaging/ubuntu/16.04/rundeps @@ -0,0 +1,2 @@ +libfstrm0 +libprotobuf-c1 diff --git a/modules/dnstap/.packaging/ubuntu/18.04/builddeps b/modules/dnstap/.packaging/ubuntu/18.04/builddeps new file mode 100644 index 0000000..417dc04 --- /dev/null +++ b/modules/dnstap/.packaging/ubuntu/18.04/builddeps @@ -0,0 +1,3 @@ +libfstrm-dev +libprotobuf-c-dev +protobuf-c-compiler diff --git a/modules/dnstap/.packaging/ubuntu/18.04/rundeps b/modules/dnstap/.packaging/ubuntu/18.04/rundeps new file mode 100644 index 0000000..a726e12 --- /dev/null +++ b/modules/dnstap/.packaging/ubuntu/18.04/rundeps @@ -0,0 +1,2 @@ +libfstrm0 +libprotobuf-c1 diff --git a/modules/dnstap/.packaging/ubuntu/20.04/builddeps b/modules/dnstap/.packaging/ubuntu/20.04/builddeps new file mode 100644 index 0000000..417dc04 --- /dev/null +++ b/modules/dnstap/.packaging/ubuntu/20.04/builddeps @@ -0,0 +1,3 @@ +libfstrm-dev +libprotobuf-c-dev +protobuf-c-compiler diff --git a/modules/dnstap/.packaging/ubuntu/20.04/rundeps b/modules/dnstap/.packaging/ubuntu/20.04/rundeps new file mode 100644 index 0000000..a726e12 --- /dev/null +++ b/modules/dnstap/.packaging/ubuntu/20.04/rundeps @@ -0,0 +1,2 @@ +libfstrm0 +libprotobuf-c1 diff --git a/modules/dnstap/README.rst b/modules/dnstap/README.rst new file mode 100644 index 0000000..456d218 --- /dev/null +++ b/modules/dnstap/README.rst @@ -0,0 +1,42 @@ +.. SPDX-License-Identifier: GPL-3.0-or-later + +.. _mod-dnstap: + +Dnstap (traffic collection) +=========================== + +The ``dnstap`` module supports logging DNS requests and responses to a unix +socket in `dnstap format <https://dnstap.info>`_ using fstrm framing library. +This logging is useful if you need effectively log all DNS traffic. + +The unix socket and the socket reader must be present before starting resolver instances. +Also it needs appropriate filesystem permissions; +the typical user and group of the daemon are called ``knot-resolver``. + +Tunables: + +* ``socket_path``: the unix socket file where dnstap messages will be sent +* ``identity``: identity string as typically returned by an "NSID" (RFC 5001) query, empty by default +* ``version``: version string of the resolver, defaulting to "Knot Resolver major.minor.patch" +* ``client.log_queries``: if ``true`` queries from downstream in wire format will be logged +* ``client.log_responses``: if ``true`` responses to downstream in wire format will be logged + +.. Very non-standard and it seems unlikely that others want to collect the RTT. +.. * ``client.log_tcp_rtt``: if ``true`` and on Linux, + add "extra" field with "rtt=12345\n", + signifying kernel's current estimate of RTT micro-seconds for the non-UDP connection + (alongside every arrived DNS message). + +.. code-block:: lua + + modules = { + dnstap = { + socket_path = "/tmp/dnstap.sock", + identity = nsid.name() or "", + version = "My Custom Knot Resolver " .. package_version(), + client = { + log_queries = true, + log_responses = true, + }, + } + } diff --git a/modules/dnstap/dnstap.c b/modules/dnstap/dnstap.c new file mode 100644 index 0000000..7572667 --- /dev/null +++ b/modules/dnstap/dnstap.c @@ -0,0 +1,524 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * + * @file dnstap.c + * @brief dnstap based query logging support + * + */ + +#include "lib/module.h" +#include "modules/dnstap/dnstap.pb-c.h" + +#include "contrib/cleanup.h" +#include "daemon/session.h" +#include "daemon/worker.h" +#include "lib/layer.h" +#include "lib/resolve.h" + +#include <ccan/json/json.h> +#include <fstrm.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <uv.h> + +#define DEBUG_MSG(fmt, ...) kr_log_debug(DNSTAP, fmt, ##__VA_ARGS__); +#define ERROR_MSG(fmt, ...) kr_log_error(DNSTAP, fmt, ##__VA_ARGS__); +#define CFG_SOCK_PATH "socket_path" +#define CFG_IDENTITY_STRING "identity" +#define CFG_VERSION_STRING "version" +#define CFG_LOG_CLIENT_PKT "client" +#define CFG_LOG_QR_PKT "log_queries" +#define CFG_LOG_RESP_PKT "log_responses" +#define CFG_LOG_TCP_RTT "log_tcp_rtt" +#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 processing phase + * Distinguishes whether query or response should be processed + */ +enum dnstap_log_phase { + CLIENT_QUERY_PHASE = 0, + CLIENT_RESPONSE_PHASE, +}; + +/* Internal data structure */ +struct dnstap_data { + char *identity; + size_t identity_len; + char *version; + size_t version_len; + bool log_qr_pkt; + bool log_resp_pkt; + bool log_tcp_rtt; + struct fstrm_iothr *iothread; + struct fstrm_iothr_queue *ioq; +}; + +/* + * dt_pack packs the dnstap message for transport + * https://gitlab.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.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; +} + +#ifndef HAS_TCP_INFO + /* TCP RTT: not portable; not sure where else it might work. */ + #define HAS_TCP_INFO __linux__ +#endif +#if HAS_TCP_INFO +/** Fill a tcp_info or return kr_error(). */ +static int get_tcp_info(const struct kr_request *req, struct tcp_info *info) +{ + if(kr_fails_assert(req && info)) + return kr_error(EINVAL); + if (!req->qsource.dst_addr || !req->qsource.flags.tcp) /* not TCP-based */ + return -abs(ENOENT); + /* First obtain the file-descriptor. */ + uv_handle_t *h = session_get_handle(worker_request_get_source_session(req)); + uv_os_fd_t fd; + int ret = uv_fileno(h, &fd); + if (ret) + return kr_error(ret); + + socklen_t tcp_info_length = sizeof(*info); + if (getsockopt(fd, SOL_TCP, TCP_INFO, info, &tcp_info_length)) + return kr_error(errno); + return kr_ok(); +} +#endif + +/* dnstap_log prepares dnstap message and sends it to fstrm + * + * Return codes are kr_error(E*) and unused for now. + */ +static int dnstap_log(kr_layer_t *ctx, enum dnstap_log_phase phase) { + 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; + + if (!req->qsource.addr) { + return kr_ok(); + } + + /* 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); + } + + /* Create dnstap message */ + Dnstap__Message m; + Dnstap__Dnstap dnstap = DNSTAP__DNSTAP__INIT; + dnstap.type = DNSTAP__DNSTAP__TYPE__MESSAGE; + dnstap.message = &m; + + memset(&m, 0, sizeof(m)); + + m.base.descriptor = &dnstap__message__descriptor; + + 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.http) { + m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__DOH; + } else if (req->qsource.flags.tls) { + m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__DOT; + } else 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; + } + } + + char dnstap_extra_buf[24]; + if (phase == CLIENT_QUERY_PHASE) { + m.type = DNSTAP__MESSAGE__TYPE__CLIENT_QUERY; + + if (dnstap_dt->log_qr_pkt) { + const knot_pkt_t *qpkt = req->qsource.packet; + m.has_query_message = qpkt != NULL; + if (qpkt != NULL) { + m.query_message.len = qpkt->size; + m.query_message.data = qpkt->wire; + } + } + + /* set query time to the timestamp of the first kr_query */ + if (rplan->initial) { + struct kr_query *first = rplan->initial; + + 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; + } +#if HAS_TCP_INFO + struct tcp_info ti = { 0 }; + if (dnstap_dt->log_tcp_rtt && get_tcp_info(req, &ti) == kr_ok()) { + int len = snprintf(dnstap_extra_buf, sizeof(dnstap_extra_buf), + "rtt=%u\n", (unsigned)ti.tcpi_rtt); + if (len < sizeof(dnstap_extra_buf)) { + dnstap.extra.data = (uint8_t *)dnstap_extra_buf; + dnstap.extra.len = len; + dnstap.has_extra = true; + } + } +#else + (void)dnstap_extra_buf; +#endif + } else if (phase == CLIENT_RESPONSE_PHASE) { + m.type = DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE; + + /* current time */ + struct timeval now; + gettimeofday(&now, NULL); + + if (dnstap_dt->log_resp_pkt) { + const knot_pkt_t *rpkt = req->answer; + m.has_response_message = rpkt != NULL; + if (rpkt != NULL) { + m.response_message.len = rpkt->size; + m.response_message.data = rpkt->wire; + } + } + + /* Set response time to now */ + 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; + } + + if (dnstap_dt->identity) { + dnstap.identity.data = (uint8_t*)dnstap_dt->identity; + dnstap.identity.len = dnstap_dt->identity_len; + dnstap.has_identity = true; + } + + if (dnstap_dt->version) { + dnstap.version.data = (uint8_t*)dnstap_dt->version; + dnstap.version.len = dnstap_dt->version_len; + dnstap.has_version = true; + } + + /* 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 kr_ok(); +} + +/* dnstap_log_query prepares dnstap CLIENT_QUERY message and sends it to fstrm */ +static int dnstap_log_query(kr_layer_t *ctx) { + dnstap_log(ctx, CLIENT_QUERY_PHASE); + return ctx->state; +} + +/* dnstap_log_response prepares dnstap CLIENT_RESPONSE message and sends it to fstrm */ +static int dnstap_log_response(kr_layer_t *ctx) { + dnstap_log(ctx, CLIENT_RESPONSE_PHASE); + return ctx->state; +} + +KR_EXPORT +int dnstap_init(struct kr_module *module) { + static kr_layer_api_t layer = { + .begin = &dnstap_log_query, + .finish = &dnstap_log_response, + }; + /* Store module reference */ + layer.data = module; + module->layer = &layer; + + /* allocated memory for internal data */ + struct dnstap_data *data = calloc(1, sizeof(*data)); + if (!data) { + return kr_error(ENOMEM); + } + + /* save pointer to internal struct in module for future reference */ + module->data = data; + return kr_ok(); +} + +/** Clear, i.e. get to state as after the first dnstap_init(). */ +static void dnstap_clear(struct kr_module *module) { + struct dnstap_data *data = module->data; + if (data) { + free(data->identity); + free(data->version); + + fstrm_iothr_destroy(&data->iothread); + DEBUG_MSG("fstrm iothread destroyed\n"); + } +} + +KR_EXPORT +int dnstap_deinit(struct kr_module *module) { + dnstap_clear(module); + free(module->data); + return kr_ok(); +} + +/* dnstap_unix_writer returns a unix fstream writer + * https://gitlab.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); + if (kr_fails_assert(node->tag == JSON_STRING)) + return kr_error(EINVAL); + *val = strndup(node->string_, len); + if (kr_fails_assert(*val != NULL)) + return kr_error(errno); + return kr_ok(); +} + +/* find_bool returns bool from json */ +static bool find_bool(const JsonNode *node) { + if (!node || !node->key) + return false; + if (kr_fails_assert(node->tag == JSON_BOOL)) + return false; + return node->bool_; +} + +/* parse config */ +KR_EXPORT +int dnstap_config(struct kr_module *module, const char *conf) { + dnstap_clear(module); + if (!conf) return kr_ok(); /* loaded module without configuring */ + struct dnstap_data *data = module->data; + auto_free char *sock_path = NULL; + + /* Empty conf passed, set default */ + if (strlen(conf) < 1) { + sock_path = strdup(DEFAULT_SOCK_PATH); + } else { + + JsonNode *root_node = json_decode(conf); + if (!root_node) { + ERROR_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 = strdup(DEFAULT_SOCK_PATH); + } + + /* identity string key */ + node = json_find_member(root_node, CFG_IDENTITY_STRING); + if (!node || find_string(node, &data->identity, KR_EDNS_PAYLOAD) != kr_ok()) { + data->identity = NULL; + data->identity_len = 0; + } else { + data->identity_len = strlen(data->identity); + } + + /* version string key */ + node = json_find_member(root_node, CFG_VERSION_STRING); + if (!node || find_string(node, &data->version, KR_EDNS_PAYLOAD) != kr_ok()) { + data->version = strdup("Knot Resolver " PACKAGE_VERSION); + if (data->version) { + data->version_len = strlen(data->version); + } + } else { + data->version_len = strlen(data->version); + } + + node = json_find_member(root_node, CFG_LOG_CLIENT_PKT); + if (node) { + JsonNode *subnode; + /* logRespPkt key */ + subnode = json_find_member(node, CFG_LOG_RESP_PKT); + if (subnode) { + data->log_resp_pkt = find_bool(subnode); + } else { + data->log_resp_pkt = false; + } + + /* logQrPkt key */ + subnode = json_find_member(node, CFG_LOG_QR_PKT); + if (subnode) { + data->log_qr_pkt = find_bool(subnode); + } else { + data->log_qr_pkt = false; + } + + subnode = json_find_member(node, CFG_LOG_TCP_RTT); + if (subnode) { + data->log_tcp_rtt = find_bool(subnode); + } else { + data->log_tcp_rtt = false; + } + } else { + data->log_qr_pkt = false; + data->log_resp_pkt = false; + data->log_tcp_rtt = 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) { + ERROR_MSG("failed to open socket %s\n" + "Please ensure that it exists beforehand and has appropriate access permissions.\n", + sock_path); + return kr_error(EINVAL); + } + + struct fstrm_iothr_options *opt = fstrm_iothr_options_init(); + if (!opt) { + ERROR_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) { + ERROR_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); + ERROR_MSG("can't get fstrm queue\n"); + return kr_error(EBUSY); + } + + return kr_ok(); +} + +KR_MODULE_EXPORT(dnstap) + diff --git a/modules/dnstap/dnstap.proto b/modules/dnstap/dnstap.proto new file mode 100644 index 0000000..f2b7273 --- /dev/null +++ b/modules/dnstap/dnstap.proto @@ -0,0 +1,273 @@ +// dnstap: flexible, structured event replication format for DNS software +// +// This file contains the protobuf schemas for the "dnstap" structured event +// replication format for DNS software. + +// Written in 2013-2014 by Farsight Security, Inc. +// +// SPDX-License-Identifier: CC0-1.0 + +syntax = "proto2"; +package dnstap; + +// "Dnstap": this is the top-level dnstap type, which is a "union" type that +// contains other kinds of dnstap payloads, although currently only one type +// of dnstap payload is defined. +// See: https://developers.google.com/protocol-buffers/docs/techniques#union +message Dnstap { + // DNS server identity. + // If enabled, this is the identity string of the DNS server which generated + // this message. Typically this would be the same string as returned by an + // "NSID" (RFC 5001) query. + optional bytes identity = 1; + + // DNS server version. + // If enabled, this is the version string of the DNS server which generated + // this message. Typically this would be the same string as returned by a + // "version.bind" query. + optional bytes version = 2; + + // Extra data for this payload. + // This field can be used for adding an arbitrary byte-string annotation to + // the payload. No encoding or interpretation is applied or enforced. + optional bytes extra = 3; + + // Identifies which field below is filled in. + enum Type { + MESSAGE = 1; + } + required Type type = 15; + + // One of the following will be filled in. + optional Message message = 14; +} + +// SocketFamily: the network protocol family of a socket. This specifies how +// to interpret "network address" fields. +enum SocketFamily { + INET = 1; // IPv4 (RFC 791) + INET6 = 2; // IPv6 (RFC 2460) +} + +// SocketProtocol: the protocol used to transport a DNS message. +enum SocketProtocol { + UDP = 1; // DNS over UDP transport (RFC 1035 section 4.2.1) + TCP = 2; // DNS over TCP transport (RFC 1035 section 4.2.2) + DOT = 3; // DNS over TLS (RFC 7858) + DOH = 4; // DNS over HTTPS (RFC 8484) +} + +// Message: a wire-format (RFC 1035 section 4) DNS message and associated +// metadata. Applications generating "Message" payloads should follow +// certain requirements based on the MessageType, see below. +message Message { + + // There are eight types of "Message" defined that correspond to the + // four arrows in the following diagram, slightly modified from RFC 1035 + // section 2: + + // +---------+ +----------+ +--------+ + // | | query | | query | | + // | Stub |-SQ--------CQ->| Recursive|-RQ----AQ->| Auth. | + // | Resolver| | Server | | Name | + // | |<-SR--------CR-| |<-RR----AR-| Server | + // +---------+ response | | response | | + // +----------+ +--------+ + + // Each arrow has two Type values each, one for each "end" of each arrow, + // because these are considered to be distinct events. Each end of each + // arrow on the diagram above has been marked with a two-letter Type + // mnemonic. Clockwise from upper left, these mnemonic values are: + // + // SQ: STUB_QUERY + // CQ: CLIENT_QUERY + // RQ: RESOLVER_QUERY + // AQ: AUTH_QUERY + // AR: AUTH_RESPONSE + // RR: RESOLVER_RESPONSE + // CR: CLIENT_RESPONSE + // SR: STUB_RESPONSE + + // Two additional types of "Message" have been defined for the + // "forwarding" case where an upstream DNS server is responsible for + // further recursion. These are not shown on the diagram above, but have + // the following mnemonic values: + + // FQ: FORWARDER_QUERY + // FR: FORWARDER_RESPONSE + + // The "Message" Type values are defined below. + + enum Type { + // AUTH_QUERY is a DNS query message received from a resolver by an + // authoritative name server, from the perspective of the authoritative + // name server. + AUTH_QUERY = 1; + + // AUTH_RESPONSE is a DNS response message sent from an authoritative + // name server to a resolver, from the perspective of the authoritative + // name server. + AUTH_RESPONSE = 2; + + // RESOLVER_QUERY is a DNS query message sent from a resolver to an + // authoritative name server, from the perspective of the resolver. + // Resolvers typically clear the RD (recursion desired) bit when + // sending queries. + RESOLVER_QUERY = 3; + + // RESOLVER_RESPONSE is a DNS response message received from an + // authoritative name server by a resolver, from the perspective of + // the resolver. + RESOLVER_RESPONSE = 4; + + // CLIENT_QUERY is a DNS query message sent from a client to a DNS + // server which is expected to perform further recursion, from the + // perspective of the DNS server. The client may be a stub resolver or + // forwarder or some other type of software which typically sets the RD + // (recursion desired) bit when querying the DNS server. The DNS server + // may be a simple forwarding proxy or it may be a full recursive + // resolver. + CLIENT_QUERY = 5; + + // CLIENT_RESPONSE is a DNS response message sent from a DNS server to + // a client, from the perspective of the DNS server. The DNS server + // typically sets the RA (recursion available) bit when responding. + CLIENT_RESPONSE = 6; + + // FORWARDER_QUERY is a DNS query message sent from a downstream DNS + // server to an upstream DNS server which is expected to perform + // further recursion, from the perspective of the downstream DNS + // server. + FORWARDER_QUERY = 7; + + // FORWARDER_RESPONSE is a DNS response message sent from an upstream + // DNS server performing recursion to a downstream DNS server, from the + // perspective of the downstream DNS server. + FORWARDER_RESPONSE = 8; + + // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS + // server, from the perspective of the stub resolver. + STUB_QUERY = 9; + + // STUB_RESPONSE is a DNS response message sent from a DNS server to a + // stub resolver, from the perspective of the stub resolver. + STUB_RESPONSE = 10; + + // TOOL_QUERY is a DNS query message sent from a DNS software tool to a + // DNS server, from the perspective of the tool. + TOOL_QUERY = 11; + + // TOOL_RESPONSE is a DNS response message received by a DNS software + // tool from a DNS server, from the perspective of the tool. + TOOL_RESPONSE = 12; + + // UPDATE_QUERY is a DNS update query message received from a resolver + // by an authoritative name server, from the perspective of the + // authoritative name server. + UPDATE_QUERY = 13; + + // UPDATE_RESPONSE is a DNS update response message sent from an + // authoritative name server to a resolver, from the perspective of the + // authoritative name server. + UPDATE_RESPONSE = 14; + } + + // One of the Type values described above. + required Type type = 1; + + // One of the SocketFamily values described above. + optional SocketFamily socket_family = 2; + + // One of the SocketProtocol values described above. + optional SocketProtocol socket_protocol = 3; + + // The network address of the message initiator. + // For SocketFamily INET, this field is 4 octets (IPv4 address). + // For SocketFamily INET6, this field is 16 octets (IPv6 address). + optional bytes query_address = 4; + + // The network address of the message responder. + // For SocketFamily INET, this field is 4 octets (IPv4 address). + // For SocketFamily INET6, this field is 16 octets (IPv6 address). + optional bytes response_address = 5; + + // The transport port of the message initiator. + // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. + optional uint32 query_port = 6; + + // The transport port of the message responder. + // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. + optional uint32 response_port = 7; + + // The time at which the DNS query message was sent or received, depending + // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY. + // This is the number of seconds since the UNIX epoch. + optional uint64 query_time_sec = 8; + + // The time at which the DNS query message was sent or received. + // This is the seconds fraction, expressed as a count of nanoseconds. + optional fixed32 query_time_nsec = 9; + + // The initiator's original wire-format DNS query message, verbatim. + optional bytes query_message = 10; + + // The "zone" or "bailiwick" pertaining to the DNS query message. + // This is a wire-format DNS domain name. + optional bytes query_zone = 11; + + // The time at which the DNS response message was sent or received, + // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or + // CLIENT_RESPONSE. + // This is the number of seconds since the UNIX epoch. + optional uint64 response_time_sec = 12; + + // The time at which the DNS response message was sent or received. + // This is the seconds fraction, expressed as a count of nanoseconds. + optional fixed32 response_time_nsec = 13; + + // The responder's original wire-format DNS response message, verbatim. + optional bytes response_message = 14; +} + +// All fields except for 'type' in the Message schema are optional. +// It is recommended that at least the following fields be filled in for +// particular types of Messages. + +// AUTH_QUERY: +// socket_family, socket_protocol +// query_address, query_port +// query_message +// query_time_sec, query_time_nsec + +// AUTH_RESPONSE: +// socket_family, socket_protocol +// query_address, query_port +// query_time_sec, query_time_nsec +// response_message +// response_time_sec, response_time_nsec + +// RESOLVER_QUERY: +// socket_family, socket_protocol +// query_message +// query_time_sec, query_time_nsec +// query_zone +// response_address, response_port + +// RESOLVER_RESPONSE: +// socket_family, socket_protocol +// query_time_sec, query_time_nsec +// query_zone +// response_address, response_port +// response_message +// response_time_sec, response_time_nsec + +// CLIENT_QUERY: +// socket_family, socket_protocol +// query_message +// query_time_sec, query_time_nsec + +// CLIENT_RESPONSE: +// socket_family, socket_protocol +// query_time_sec, query_time_nsec +// response_message +// response_time_sec, response_time_nsec diff --git a/modules/dnstap/meson.build b/modules/dnstap/meson.build new file mode 100644 index 0000000..e8a94bf --- /dev/null +++ b/modules/dnstap/meson.build @@ -0,0 +1,57 @@ +# C module: dnstap +# SPDX-License-Identifier: GPL-3.0-or-later + +dnstap_src = files([ + 'dnstap.c', +]) + +## dnstap dependencies +build_dnstap = false +if get_option('dnstap') != 'disabled' + dnstap_required = get_option('dnstap') == 'enabled' + message('--- dnstap module dependencies ---') + libprotobuf_c = dependency('libprotobuf-c', version: '>=1', required: dnstap_required) + libfstrm = dependency('libfstrm', version: '>=0.2', required: dnstap_required) + protoc_c = find_program('protoc-c', required: dnstap_required) + message('----------------------------------') + if libprotobuf_c.found() and libfstrm.found() and protoc_c.found() + build_dnstap = true + endif +endif + + +if build_dnstap + c_src_lint += dnstap_src + + # generate protobuf-c sources using protoc-c + dnstap_pb = custom_target( + 'dnstap_pb', + command: [ + protoc_c, + '--c_out=' + meson.current_build_dir(), + '--proto_path', meson.current_source_dir(), + meson.current_source_dir() / 'dnstap.proto', + ], + input: [ 'dnstap.proto' ], + output: [ + 'dnstap.pb-c.h', + 'dnstap.pb-c.c', + ], + ) + + # build dnstap module + dnstap_mod = shared_module( + 'dnstap', + dnstap_src, + dependencies: [ + declare_dependency(sources: dnstap_pb), + libfstrm, + libprotobuf_c, + libknot, + ], + include_directories: mod_inc_dir, + name_prefix: '', + install: true, + install_dir: modules_dir, + ) +endif |