diff options
Diffstat (limited to '')
-rw-r--r-- | modules/dnstap/dnstap.c | 382 |
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) + |