/* $NetBSD: dnstap.c,v 1.12.2.1 2024/02/25 15:46:49 martin Exp $ */ /* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. * * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ /* * Copyright (c) 2013-2014, Farsight Security, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*! \file */ #ifndef HAVE_DNSTAP #error DNSTAP not configured. #endif /* HAVE_DNSTAP */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dnstap.pb-c.h" #define DTENV_MAGIC ISC_MAGIC('D', 't', 'n', 'v') #define VALID_DTENV(env) ISC_MAGIC_VALID(env, DTENV_MAGIC) #define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap" #define DNSTAP_INITIAL_BUF_SIZE 256 struct dns_dtmsg { void *buf; size_t len; Dnstap__Dnstap d; Dnstap__Message m; }; struct dns_dthandle { dns_dtmode_t mode; struct fstrm_reader *reader; isc_mem_t *mctx; }; struct dns_dtenv { unsigned int magic; isc_refcount_t refcount; isc_mem_t *mctx; struct fstrm_iothr *iothr; struct fstrm_iothr_options *fopt; isc_task_t *reopen_task; isc_mutex_t reopen_lock; /* locks 'reopen_queued' * */ bool reopen_queued; isc_region_t identity; isc_region_t version; char *path; dns_dtmode_t mode; isc_offset_t max_size; int rolls; isc_log_rollsuffix_t suffix; isc_stats_t *stats; }; #define CHECK(x) \ do { \ result = (x); \ if (result != ISC_R_SUCCESS) \ goto cleanup; \ } while (0) typedef struct ioq { unsigned int generation; struct fstrm_iothr_queue *ioq; } dt__ioq_t; static thread_local dt__ioq_t dt_ioq = { 0 }; static atomic_uint_fast32_t global_generation; isc_result_t dns_dt_create(isc_mem_t *mctx, dns_dtmode_t mode, const char *path, struct fstrm_iothr_options **foptp, isc_task_t *reopen_task, dns_dtenv_t **envp) { isc_result_t result = ISC_R_SUCCESS; fstrm_res res; struct fstrm_unix_writer_options *fuwopt = NULL; struct fstrm_file_options *ffwopt = NULL; struct fstrm_writer_options *fwopt = NULL; struct fstrm_writer *fw = NULL; dns_dtenv_t *env = NULL; REQUIRE(path != NULL); REQUIRE(envp != NULL && *envp == NULL); REQUIRE(foptp != NULL && *foptp != NULL); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP, ISC_LOG_INFO, "opening dnstap destination '%s'", path); atomic_fetch_add_release(&global_generation, 1); env = isc_mem_get(mctx, sizeof(dns_dtenv_t)); memset(env, 0, sizeof(dns_dtenv_t)); isc_mem_attach(mctx, &env->mctx); env->reopen_task = reopen_task; isc_mutex_init(&env->reopen_lock); env->reopen_queued = false; env->path = isc_mem_strdup(env->mctx, path); isc_refcount_init(&env->refcount, 1); CHECK(isc_stats_create(env->mctx, &env->stats, dns_dnstapcounter_max)); fwopt = fstrm_writer_options_init(); if (fwopt == NULL) { CHECK(ISC_R_NOMEMORY); } res = fstrm_writer_options_add_content_type( fwopt, DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1); if (res != fstrm_res_success) { CHECK(ISC_R_FAILURE); } if (mode == dns_dtmode_file) { ffwopt = fstrm_file_options_init(); if (ffwopt != NULL) { fstrm_file_options_set_file_path(ffwopt, env->path); fw = fstrm_file_writer_init(ffwopt, fwopt); } } else if (mode == dns_dtmode_unix) { fuwopt = fstrm_unix_writer_options_init(); if (fuwopt != NULL) { fstrm_unix_writer_options_set_socket_path(fuwopt, env->path); fw = fstrm_unix_writer_init(fuwopt, fwopt); } } else { CHECK(ISC_R_FAILURE); } if (fw == NULL) { CHECK(ISC_R_FAILURE); } env->iothr = fstrm_iothr_init(*foptp, &fw); if (env->iothr == NULL) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP, ISC_LOG_WARNING, "unable to initialize dnstap I/O thread"); fstrm_writer_destroy(&fw); CHECK(ISC_R_FAILURE); } env->mode = mode; env->max_size = 0; env->rolls = ISC_LOG_ROLLINFINITE; env->fopt = *foptp; *foptp = NULL; env->magic = DTENV_MAGIC; *envp = env; cleanup: if (ffwopt != NULL) { fstrm_file_options_destroy(&ffwopt); } if (fuwopt != NULL) { fstrm_unix_writer_options_destroy(&fuwopt); } if (fwopt != NULL) { fstrm_writer_options_destroy(&fwopt); } if (result != ISC_R_SUCCESS) { isc_mutex_destroy(&env->reopen_lock); isc_mem_free(env->mctx, env->path); if (env->stats != NULL) { isc_stats_detach(&env->stats); } isc_mem_putanddetach(&env->mctx, env, sizeof(dns_dtenv_t)); } return (result); } isc_result_t dns_dt_setupfile(dns_dtenv_t *env, uint64_t max_size, int rolls, isc_log_rollsuffix_t suffix) { REQUIRE(VALID_DTENV(env)); /* * If we're using unix domain socket mode, then any * change from the default values is invalid. */ if (env->mode == dns_dtmode_unix) { if (max_size == 0 && rolls == ISC_LOG_ROLLINFINITE && suffix == isc_log_rollsuffix_increment) { return (ISC_R_SUCCESS); } else { return (ISC_R_INVALIDFILE); } } env->max_size = max_size; env->rolls = rolls; env->suffix = suffix; return (ISC_R_SUCCESS); } isc_result_t dns_dt_reopen(dns_dtenv_t *env, int roll) { isc_result_t result = ISC_R_SUCCESS; fstrm_res res; isc_logfile_t file; struct fstrm_unix_writer_options *fuwopt = NULL; struct fstrm_file_options *ffwopt = NULL; struct fstrm_writer_options *fwopt = NULL; struct fstrm_writer *fw = NULL; REQUIRE(VALID_DTENV(env)); /* * Run in task-exclusive mode. */ result = isc_task_beginexclusive(env->reopen_task); RUNTIME_CHECK(result == ISC_R_SUCCESS); /* * Check that we can create a new fw object. */ fwopt = fstrm_writer_options_init(); if (fwopt == NULL) { CHECK(ISC_R_NOMEMORY); } res = fstrm_writer_options_add_content_type( fwopt, DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1); if (res != fstrm_res_success) { CHECK(ISC_R_FAILURE); } if (env->mode == dns_dtmode_file) { ffwopt = fstrm_file_options_init(); if (ffwopt != NULL) { fstrm_file_options_set_file_path(ffwopt, env->path); fw = fstrm_file_writer_init(ffwopt, fwopt); } } else if (env->mode == dns_dtmode_unix) { fuwopt = fstrm_unix_writer_options_init(); if (fuwopt != NULL) { fstrm_unix_writer_options_set_socket_path(fuwopt, env->path); fw = fstrm_unix_writer_init(fuwopt, fwopt); } } else { CHECK(ISC_R_NOTIMPLEMENTED); } if (fw == NULL) { CHECK(ISC_R_FAILURE); } /* * We are committed here. */ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP, ISC_LOG_INFO, "%s dnstap destination '%s'", (roll < 0) ? "reopening" : "rolling", env->path); atomic_fetch_add_release(&global_generation, 1); if (env->iothr != NULL) { fstrm_iothr_destroy(&env->iothr); } if (roll == 0) { roll = env->rolls; } if (env->mode == dns_dtmode_file && roll != 0) { /* * Create a temporary isc_logfile_t structure so we can * take advantage of the logfile rolling facility. */ char *filename = isc_mem_strdup(env->mctx, env->path); file.name = filename; file.stream = NULL; file.versions = roll; file.maximum_size = 0; file.maximum_reached = false; file.suffix = env->suffix; result = isc_logfile_roll(&file); isc_mem_free(env->mctx, filename); CHECK(result); } env->iothr = fstrm_iothr_init(env->fopt, &fw); if (env->iothr == NULL) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP, ISC_LOG_WARNING, "unable to initialize dnstap I/O thread"); CHECK(ISC_R_FAILURE); } cleanup: if (fw != NULL) { fstrm_writer_destroy(&fw); } if (fuwopt != NULL) { fstrm_unix_writer_options_destroy(&fuwopt); } if (ffwopt != NULL) { fstrm_file_options_destroy(&ffwopt); } if (fwopt != NULL) { fstrm_writer_options_destroy(&fwopt); } isc_task_endexclusive(env->reopen_task); return (result); } static isc_result_t toregion(dns_dtenv_t *env, isc_region_t *r, const char *str) { unsigned char *p = NULL; REQUIRE(r != NULL); if (str != NULL) { p = (unsigned char *)isc_mem_strdup(env->mctx, str); } if (r->base != NULL) { isc_mem_free(env->mctx, r->base); r->length = 0; } if (p != NULL) { r->base = p; r->length = strlen((char *)p); } return (ISC_R_SUCCESS); } isc_result_t dns_dt_setidentity(dns_dtenv_t *env, const char *identity) { REQUIRE(VALID_DTENV(env)); return (toregion(env, &env->identity, identity)); } isc_result_t dns_dt_setversion(dns_dtenv_t *env, const char *version) { REQUIRE(VALID_DTENV(env)); return (toregion(env, &env->version, version)); } static void set_dt_ioq(unsigned int generation, struct fstrm_iothr_queue *ioq) { dt_ioq.generation = generation; dt_ioq.ioq = ioq; } static struct fstrm_iothr_queue * dt_queue(dns_dtenv_t *env) { REQUIRE(VALID_DTENV(env)); unsigned int generation; if (env->iothr == NULL) { return (NULL); } generation = atomic_load_acquire(&global_generation); if (dt_ioq.ioq != NULL && dt_ioq.generation != generation) { set_dt_ioq(0, NULL); } if (dt_ioq.ioq == NULL) { struct fstrm_iothr_queue *ioq = fstrm_iothr_get_input_queue(env->iothr); set_dt_ioq(generation, ioq); } return (dt_ioq.ioq); } void dns_dt_attach(dns_dtenv_t *source, dns_dtenv_t **destp) { REQUIRE(VALID_DTENV(source)); REQUIRE(destp != NULL && *destp == NULL); isc_refcount_increment(&source->refcount); *destp = source; } isc_result_t dns_dt_getstats(dns_dtenv_t *env, isc_stats_t **statsp) { REQUIRE(VALID_DTENV(env)); REQUIRE(statsp != NULL && *statsp == NULL); if (env->stats == NULL) { return (ISC_R_NOTFOUND); } isc_stats_attach(env->stats, statsp); return (ISC_R_SUCCESS); } static void destroy(dns_dtenv_t *env) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP, ISC_LOG_INFO, "closing dnstap"); env->magic = 0; atomic_fetch_add(&global_generation, 1); if (env->iothr != NULL) { fstrm_iothr_destroy(&env->iothr); } if (env->fopt != NULL) { fstrm_iothr_options_destroy(&env->fopt); } if (env->identity.base != NULL) { isc_mem_free(env->mctx, env->identity.base); env->identity.length = 0; } if (env->version.base != NULL) { isc_mem_free(env->mctx, env->version.base); env->version.length = 0; } if (env->path != NULL) { isc_mem_free(env->mctx, env->path); } if (env->stats != NULL) { isc_stats_detach(&env->stats); } isc_mem_putanddetach(&env->mctx, env, sizeof(*env)); } void dns_dt_detach(dns_dtenv_t **envp) { REQUIRE(envp != NULL && VALID_DTENV(*envp)); dns_dtenv_t *env = *envp; *envp = NULL; if (isc_refcount_decrement(&env->refcount) == 1) { isc_refcount_destroy(&env->refcount); destroy(env); } } static isc_result_t pack_dt(const Dnstap__Dnstap *d, void **buf, size_t *sz) { ProtobufCBufferSimple sbuf; REQUIRE(d != NULL); REQUIRE(sz != NULL); memset(&sbuf, 0, sizeof(sbuf)); sbuf.base.append = protobuf_c_buffer_simple_append; sbuf.len = 0; sbuf.alloced = DNSTAP_INITIAL_BUF_SIZE; /* Need to use malloc() here because protobuf uses free() */ sbuf.data = malloc(sbuf.alloced); if (sbuf.data == NULL) { return (ISC_R_NOMEMORY); } sbuf.must_free_data = 1; *sz = dnstap__dnstap__pack_to_buffer(d, (ProtobufCBuffer *)&sbuf); if (sbuf.data == NULL) { return (ISC_R_FAILURE); } *buf = sbuf.data; return (ISC_R_SUCCESS); } static void send_dt(dns_dtenv_t *env, void *buf, size_t len) { struct fstrm_iothr_queue *ioq; fstrm_res res; REQUIRE(env != NULL); if (buf == NULL) { return; } ioq = dt_queue(env); if (ioq == NULL) { free(buf); return; } res = fstrm_iothr_submit(env->iothr, ioq, buf, len, fstrm_free_wrapper, NULL); if (res != fstrm_res_success) { if (env->stats != NULL) { isc_stats_increment(env->stats, dns_dnstapcounter_drop); } free(buf); } else { if (env->stats != NULL) { isc_stats_increment(env->stats, dns_dnstapcounter_success); } } } static void init_msg(dns_dtenv_t *env, dns_dtmsg_t *dm, Dnstap__Message__Type mtype) { memset(dm, 0, sizeof(*dm)); dm->d.base.descriptor = &dnstap__dnstap__descriptor; dm->m.base.descriptor = &dnstap__message__descriptor; dm->d.type = DNSTAP__DNSTAP__TYPE__MESSAGE; dm->d.message = &dm->m; dm->m.type = mtype; if (env->identity.length != 0) { dm->d.identity.data = env->identity.base; dm->d.identity.len = env->identity.length; dm->d.has_identity = true; } if (env->version.length != 0) { dm->d.version.data = env->version.base; dm->d.version.len = env->version.length; dm->d.has_version = true; } } static Dnstap__Message__Type dnstap_type(dns_dtmsgtype_t msgtype) { switch (msgtype) { case DNS_DTTYPE_SQ: return (DNSTAP__MESSAGE__TYPE__STUB_QUERY); case DNS_DTTYPE_SR: return (DNSTAP__MESSAGE__TYPE__STUB_RESPONSE); case DNS_DTTYPE_CQ: return (DNSTAP__MESSAGE__TYPE__CLIENT_QUERY); case DNS_DTTYPE_CR: return (DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE); case DNS_DTTYPE_AQ: return (DNSTAP__MESSAGE__TYPE__AUTH_QUERY); case DNS_DTTYPE_AR: return (DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE); case DNS_DTTYPE_RQ: return (DNSTAP__MESSAGE__TYPE__RESOLVER_QUERY); case DNS_DTTYPE_RR: return (DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE); case DNS_DTTYPE_FQ: return (DNSTAP__MESSAGE__TYPE__FORWARDER_QUERY); case DNS_DTTYPE_FR: return (DNSTAP__MESSAGE__TYPE__FORWARDER_RESPONSE); case DNS_DTTYPE_TQ: return (DNSTAP__MESSAGE__TYPE__TOOL_QUERY); case DNS_DTTYPE_TR: return (DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE); case DNS_DTTYPE_UQ: return (DNSTAP__MESSAGE__TYPE__UPDATE_QUERY); case DNS_DTTYPE_UR: return (DNSTAP__MESSAGE__TYPE__UPDATE_RESPONSE); default: UNREACHABLE(); } } static void cpbuf(isc_buffer_t *buf, ProtobufCBinaryData *p, protobuf_c_boolean *has) { p->data = isc_buffer_base(buf); p->len = isc_buffer_usedlength(buf); *has = 1; } static void setaddr(dns_dtmsg_t *dm, isc_sockaddr_t *sa, bool tcp, ProtobufCBinaryData *addr, protobuf_c_boolean *has_addr, uint32_t *port, protobuf_c_boolean *has_port) { int family = isc_sockaddr_pf(sa); if (family != AF_INET6 && family != AF_INET) { return; } if (family == AF_INET6) { dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET6; addr->data = sa->type.sin6.sin6_addr.s6_addr; addr->len = 16; *port = ntohs(sa->type.sin6.sin6_port); } else { dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET; addr->data = (uint8_t *)&sa->type.sin.sin_addr.s_addr; addr->len = 4; *port = ntohs(sa->type.sin.sin_port); } if (tcp) { dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__TCP; } else { dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__UDP; } dm->m.has_socket_protocol = 1; dm->m.has_socket_family = 1; *has_addr = 1; *has_port = 1; } /*% * Invoke dns_dt_reopen() and re-allow dnstap output file rolling. This * function is run in the context of the task stored in the 'reopen_task' field * of the dnstap environment structure. */ static void perform_reopen(isc_task_t *task, isc_event_t *event) { dns_dtenv_t *env; REQUIRE(event != NULL); REQUIRE(event->ev_type == DNS_EVENT_FREESTORAGE); env = (dns_dtenv_t *)event->ev_arg; REQUIRE(VALID_DTENV(env)); REQUIRE(task == env->reopen_task); /* * Roll output file in the context of env->reopen_task. */ dns_dt_reopen(env, env->rolls); /* * Clean up. */ isc_event_free(&event); isc_task_detach(&task); /* * Re-allow output file rolling. */ LOCK(&env->reopen_lock); env->reopen_queued = false; UNLOCK(&env->reopen_lock); } /*% * Check whether a dnstap output file roll is due and if so, initiate it (the * actual roll happens asynchronously). */ static void check_file_size_and_maybe_reopen(dns_dtenv_t *env) { isc_task_t *reopen_task = NULL; isc_event_t *event; struct stat statbuf; /* * If the task from which the output file should be reopened was not * specified, abort. */ if (env->reopen_task == NULL) { return; } /* * If an output file roll is not currently queued, check the current * size of the output file to see whether a roll is needed. Return if * it is not. */ LOCK(&env->reopen_lock); if (env->reopen_queued || stat(env->path, &statbuf) < 0 || statbuf.st_size <= env->max_size) { goto unlock_and_return; } /* * We need to roll the output file, but it needs to be done in the * context of env->reopen_task. Allocate and send an event to achieve * that, then disallow output file rolling until the roll we queue is * completed. */ event = isc_event_allocate(env->mctx, NULL, DNS_EVENT_FREESTORAGE, perform_reopen, env, sizeof(*event)); isc_task_attach(env->reopen_task, &reopen_task); isc_task_send(reopen_task, &event); env->reopen_queued = true; unlock_and_return: UNLOCK(&env->reopen_lock); } void dns_dt_send(dns_view_t *view, dns_dtmsgtype_t msgtype, isc_sockaddr_t *qaddr, isc_sockaddr_t *raddr, bool tcp, isc_region_t *zone, isc_time_t *qtime, isc_time_t *rtime, isc_buffer_t *buf) { isc_time_t now, *t; dns_dtmsg_t dm; REQUIRE(DNS_VIEW_VALID(view)); if ((msgtype & view->dttypes) == 0) { return; } if (view->dtenv == NULL) { return; } REQUIRE(VALID_DTENV(view->dtenv)); if (view->dtenv->max_size != 0) { check_file_size_and_maybe_reopen(view->dtenv); } TIME_NOW(&now); t = &now; init_msg(view->dtenv, &dm, dnstap_type(msgtype)); /* Query/response times */ switch (msgtype) { case DNS_DTTYPE_AR: case DNS_DTTYPE_CR: case DNS_DTTYPE_RR: case DNS_DTTYPE_FR: case DNS_DTTYPE_SR: case DNS_DTTYPE_TR: case DNS_DTTYPE_UR: if (rtime != NULL) { t = rtime; } dm.m.response_time_sec = isc_time_seconds(t); dm.m.has_response_time_sec = 1; dm.m.response_time_nsec = isc_time_nanoseconds(t); dm.m.has_response_time_nsec = 1; /* * Types RR and FR can fall through and get the query * time set as well. Any other response type, break. */ if (msgtype != DNS_DTTYPE_RR && msgtype != DNS_DTTYPE_FR) { break; } FALLTHROUGH; case DNS_DTTYPE_AQ: case DNS_DTTYPE_CQ: case DNS_DTTYPE_FQ: case DNS_DTTYPE_RQ: case DNS_DTTYPE_SQ: case DNS_DTTYPE_TQ: case DNS_DTTYPE_UQ: if (qtime != NULL) { t = qtime; } dm.m.query_time_sec = isc_time_seconds(t); dm.m.has_query_time_sec = 1; dm.m.query_time_nsec = isc_time_nanoseconds(t); dm.m.has_query_time_nsec = 1; break; default: isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP, ISC_LOG_ERROR, "invalid dnstap message type %d", msgtype); return; } /* Query and response messages */ if ((msgtype & DNS_DTTYPE_QUERY) != 0) { cpbuf(buf, &dm.m.query_message, &dm.m.has_query_message); } else if ((msgtype & DNS_DTTYPE_RESPONSE) != 0) { cpbuf(buf, &dm.m.response_message, &dm.m.has_response_message); } /* Zone/bailiwick */ switch (msgtype) { case DNS_DTTYPE_AR: case DNS_DTTYPE_RQ: case DNS_DTTYPE_RR: case DNS_DTTYPE_FQ: case DNS_DTTYPE_FR: if (zone != NULL && zone->base != NULL && zone->length != 0) { dm.m.query_zone.data = zone->base; dm.m.query_zone.len = zone->length; dm.m.has_query_zone = 1; } break; default: break; } if (qaddr != NULL) { setaddr(&dm, qaddr, tcp, &dm.m.query_address, &dm.m.has_query_address, &dm.m.query_port, &dm.m.has_query_port); } if (raddr != NULL) { setaddr(&dm, raddr, tcp, &dm.m.response_address, &dm.m.has_response_address, &dm.m.response_port, &dm.m.has_response_port); } if (pack_dt(&dm.d, &dm.buf, &dm.len) == ISC_R_SUCCESS) { send_dt(view->dtenv, dm.buf, dm.len); } } static isc_result_t putstr(isc_buffer_t **b, const char *str) { isc_result_t result; result = isc_buffer_reserve(b, strlen(str)); if (result != ISC_R_SUCCESS) { return (ISC_R_NOSPACE); } isc_buffer_putstr(*b, str); return (ISC_R_SUCCESS); } static isc_result_t putaddr(isc_buffer_t **b, isc_region_t *ip) { char buf[64]; if (ip->length == 4) { if (!inet_ntop(AF_INET, ip->base, buf, sizeof(buf))) { return (ISC_R_FAILURE); } } else if (ip->length == 16) { if (!inet_ntop(AF_INET6, ip->base, buf, sizeof(buf))) { return (ISC_R_FAILURE); } } else { return (ISC_R_BADADDRESSFORM); } return (putstr(b, buf)); } static bool dnstap_file(struct fstrm_reader *r) { fstrm_res res; const struct fstrm_control *control = NULL; const uint8_t *rtype = NULL; size_t dlen = strlen(DNSTAP_CONTENT_TYPE), rlen = 0; size_t n = 0; res = fstrm_reader_get_control(r, FSTRM_CONTROL_START, &control); if (res != fstrm_res_success) { return (false); } res = fstrm_control_get_num_field_content_type(control, &n); if (res != fstrm_res_success) { return (false); } if (n > 0) { res = fstrm_control_get_field_content_type(control, 0, &rtype, &rlen); if (res != fstrm_res_success) { return (false); } if (rlen != dlen) { return (false); } if (memcmp(DNSTAP_CONTENT_TYPE, rtype, dlen) == 0) { return (true); } } return (false); } isc_result_t dns_dt_open(const char *filename, dns_dtmode_t mode, isc_mem_t *mctx, dns_dthandle_t **handlep) { isc_result_t result; struct fstrm_file_options *fopt = NULL; fstrm_res res; dns_dthandle_t *handle; REQUIRE(handlep != NULL && *handlep == NULL); handle = isc_mem_get(mctx, sizeof(*handle)); handle->mode = mode; handle->mctx = NULL; switch (mode) { case dns_dtmode_file: fopt = fstrm_file_options_init(); if (fopt == NULL) { CHECK(ISC_R_NOMEMORY); } fstrm_file_options_set_file_path(fopt, filename); handle->reader = fstrm_file_reader_init(fopt, NULL); if (handle->reader == NULL) { CHECK(ISC_R_NOMEMORY); } res = fstrm_reader_open(handle->reader); if (res != fstrm_res_success) { CHECK(ISC_R_FAILURE); } if (!dnstap_file(handle->reader)) { CHECK(DNS_R_BADDNSTAP); } break; case dns_dtmode_unix: result = ISC_R_NOTIMPLEMENTED; goto cleanup; default: UNREACHABLE(); } isc_mem_attach(mctx, &handle->mctx); result = ISC_R_SUCCESS; *handlep = handle; handle = NULL; cleanup: if (result != ISC_R_SUCCESS && handle->reader != NULL) { fstrm_reader_destroy(&handle->reader); handle->reader = NULL; } if (fopt != NULL) { fstrm_file_options_destroy(&fopt); } if (handle != NULL) { isc_mem_put(mctx, handle, sizeof(*handle)); } return (result); } isc_result_t dns_dt_getframe(dns_dthandle_t *handle, uint8_t **bufp, size_t *sizep) { const uint8_t *data; fstrm_res res; REQUIRE(handle != NULL); REQUIRE(bufp != NULL); REQUIRE(sizep != NULL); data = (const uint8_t *)*bufp; res = fstrm_reader_read(handle->reader, &data, sizep); switch (res) { case fstrm_res_success: if (data == NULL) { return (ISC_R_FAILURE); } DE_CONST(data, *bufp); return (ISC_R_SUCCESS); case fstrm_res_stop: return (ISC_R_NOMORE); default: return (ISC_R_FAILURE); } } void dns_dt_close(dns_dthandle_t **handlep) { dns_dthandle_t *handle; REQUIRE(handlep != NULL && *handlep != NULL); handle = *handlep; *handlep = NULL; if (handle->reader != NULL) { fstrm_reader_destroy(&handle->reader); handle->reader = NULL; } isc_mem_putanddetach(&handle->mctx, handle, sizeof(*handle)); } isc_result_t dns_dt_parse(isc_mem_t *mctx, isc_region_t *src, dns_dtdata_t **destp) { isc_result_t result; Dnstap__Dnstap *frame; Dnstap__Message *m; dns_dtdata_t *d = NULL; isc_buffer_t b; REQUIRE(src != NULL); REQUIRE(destp != NULL && *destp == NULL); d = isc_mem_get(mctx, sizeof(*d)); memset(d, 0, sizeof(*d)); isc_mem_attach(mctx, &d->mctx); d->frame = dnstap__dnstap__unpack(NULL, src->length, src->base); if (d->frame == NULL) { CHECK(ISC_R_NOMEMORY); } frame = (Dnstap__Dnstap *)d->frame; if (frame->type != DNSTAP__DNSTAP__TYPE__MESSAGE) { CHECK(DNS_R_BADDNSTAP); } m = frame->message; /* Message type */ switch (m->type) { case DNSTAP__MESSAGE__TYPE__AUTH_QUERY: d->type = DNS_DTTYPE_AQ; break; case DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE: d->type = DNS_DTTYPE_AR; break; case DNSTAP__MESSAGE__TYPE__CLIENT_QUERY: d->type = DNS_DTTYPE_CQ; break; case DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE: d->type = DNS_DTTYPE_CR; break; case DNSTAP__MESSAGE__TYPE__FORWARDER_QUERY: d->type = DNS_DTTYPE_FQ; break; case DNSTAP__MESSAGE__TYPE__FORWARDER_RESPONSE: d->type = DNS_DTTYPE_FR; break; case DNSTAP__MESSAGE__TYPE__RESOLVER_QUERY: d->type = DNS_DTTYPE_RQ; break; case DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE: d->type = DNS_DTTYPE_RR; break; case DNSTAP__MESSAGE__TYPE__STUB_QUERY: d->type = DNS_DTTYPE_SQ; break; case DNSTAP__MESSAGE__TYPE__STUB_RESPONSE: d->type = DNS_DTTYPE_SR; break; case DNSTAP__MESSAGE__TYPE__TOOL_QUERY: d->type = DNS_DTTYPE_TQ; break; case DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE: d->type = DNS_DTTYPE_TR; break; case DNSTAP__MESSAGE__TYPE__UPDATE_QUERY: d->type = DNS_DTTYPE_UQ; break; case DNSTAP__MESSAGE__TYPE__UPDATE_RESPONSE: d->type = DNS_DTTYPE_UR; break; default: CHECK(DNS_R_BADDNSTAP); } /* Query? */ if ((d->type & DNS_DTTYPE_QUERY) != 0) { d->query = true; } else { d->query = false; } /* Parse DNS message */ if (d->query && m->has_query_message) { d->msgdata.base = m->query_message.data; d->msgdata.length = m->query_message.len; } else if (!d->query && m->has_response_message) { d->msgdata.base = m->response_message.data; d->msgdata.length = m->response_message.len; } isc_buffer_init(&b, d->msgdata.base, d->msgdata.length); isc_buffer_add(&b, d->msgdata.length); dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &d->msg); result = dns_message_parse(d->msg, &b, 0); if (result != ISC_R_SUCCESS) { if (result != DNS_R_RECOVERABLE) { dns_message_detach(&d->msg); } result = ISC_R_SUCCESS; } /* Timestamp */ if (d->query) { if (m->has_query_time_sec && m->has_query_time_nsec) { isc_time_set(&d->qtime, m->query_time_sec, m->query_time_nsec); } } else { if (m->has_response_time_sec && m->has_response_time_nsec) { isc_time_set(&d->rtime, m->response_time_sec, m->response_time_nsec); } } /* Peer address */ if (m->has_query_address) { d->qaddr.base = m->query_address.data; d->qaddr.length = m->query_address.len; } if (m->has_query_port) { d->qport = m->query_port; } if (m->has_response_address) { d->raddr.base = m->response_address.data; d->raddr.length = m->response_address.len; } if (m->has_response_port) { d->rport = m->response_port; } /* Socket protocol */ if (m->has_socket_protocol) { const ProtobufCEnumValue *type = protobuf_c_enum_descriptor_get_value( &dnstap__socket_protocol__descriptor, m->socket_protocol); if (type != NULL && type->value == DNSTAP__SOCKET_PROTOCOL__TCP) { d->tcp = true; } else { d->tcp = false; } } /* Query tuple */ if (d->msg != NULL) { dns_name_t *name = NULL; dns_rdataset_t *rdataset; CHECK(dns_message_firstname(d->msg, DNS_SECTION_QUESTION)); dns_message_currentname(d->msg, DNS_SECTION_QUESTION, &name); rdataset = ISC_LIST_HEAD(name->list); dns_name_format(name, d->namebuf, sizeof(d->namebuf)); dns_rdatatype_format(rdataset->type, d->typebuf, sizeof(d->typebuf)); dns_rdataclass_format(rdataset->rdclass, d->classbuf, sizeof(d->classbuf)); } *destp = d; cleanup: if (result != ISC_R_SUCCESS) { dns_dtdata_free(&d); } return (result); } isc_result_t dns_dt_datatotext(dns_dtdata_t *d, isc_buffer_t **dest) { isc_result_t result; char buf[100]; REQUIRE(d != NULL); REQUIRE(dest != NULL && *dest != NULL); memset(buf, 0, sizeof(buf)); /* Timestamp */ if (d->query && !isc_time_isepoch(&d->qtime)) { isc_time_formattimestamp(&d->qtime, buf, sizeof(buf)); } else if (!d->query && !isc_time_isepoch(&d->rtime)) { isc_time_formattimestamp(&d->rtime, buf, sizeof(buf)); } if (buf[0] == '\0') { CHECK(putstr(dest, "???\?-?\?-?? ??:??:??.??? ")); } else { CHECK(putstr(dest, buf)); CHECK(putstr(dest, " ")); } /* Type mnemonic */ switch (d->type) { case DNS_DTTYPE_AQ: CHECK(putstr(dest, "AQ ")); break; case DNS_DTTYPE_AR: CHECK(putstr(dest, "AR ")); break; case DNS_DTTYPE_CQ: CHECK(putstr(dest, "CQ ")); break; case DNS_DTTYPE_CR: CHECK(putstr(dest, "CR ")); break; case DNS_DTTYPE_FQ: CHECK(putstr(dest, "FQ ")); break; case DNS_DTTYPE_FR: CHECK(putstr(dest, "FR ")); break; case DNS_DTTYPE_RQ: CHECK(putstr(dest, "RQ ")); break; case DNS_DTTYPE_RR: CHECK(putstr(dest, "RR ")); break; case DNS_DTTYPE_SQ: CHECK(putstr(dest, "SQ ")); break; case DNS_DTTYPE_SR: CHECK(putstr(dest, "SR ")); break; case DNS_DTTYPE_TQ: CHECK(putstr(dest, "TQ ")); break; case DNS_DTTYPE_TR: CHECK(putstr(dest, "TR ")); break; case DNS_DTTYPE_UQ: CHECK(putstr(dest, "UQ ")); break; case DNS_DTTYPE_UR: CHECK(putstr(dest, "UR ")); break; default: return (DNS_R_BADDNSTAP); } /* Query and response addresses */ if (d->qaddr.length != 0) { CHECK(putaddr(dest, &d->qaddr)); snprintf(buf, sizeof(buf), ":%u", d->qport); CHECK(putstr(dest, buf)); } else { CHECK(putstr(dest, "?")); } if ((d->type & DNS_DTTYPE_QUERY) != 0) { CHECK(putstr(dest, " -> ")); } else { CHECK(putstr(dest, " <- ")); } if (d->raddr.length != 0) { CHECK(putaddr(dest, &d->raddr)); snprintf(buf, sizeof(buf), ":%u", d->rport); CHECK(putstr(dest, buf)); } else { CHECK(putstr(dest, "?")); } CHECK(putstr(dest, " ")); /* Protocol */ if (d->tcp) { CHECK(putstr(dest, "TCP ")); } else { CHECK(putstr(dest, "UDP ")); } /* Message size */ if (d->msgdata.base != NULL) { snprintf(buf, sizeof(buf), "%zub ", (size_t)d->msgdata.length); CHECK(putstr(dest, buf)); } else { CHECK(putstr(dest, "0b ")); } /* Query tuple */ if (d->namebuf[0] == '\0') { CHECK(putstr(dest, "?/")); } else { CHECK(putstr(dest, d->namebuf)); CHECK(putstr(dest, "/")); } if (d->classbuf[0] == '\0') { CHECK(putstr(dest, "?/")); } else { CHECK(putstr(dest, d->classbuf)); CHECK(putstr(dest, "/")); } if (d->typebuf[0] == '\0') { CHECK(putstr(dest, "?")); } else { CHECK(putstr(dest, d->typebuf)); } CHECK(isc_buffer_reserve(dest, 1)); isc_buffer_putuint8(*dest, 0); cleanup: return (result); } void dns_dtdata_free(dns_dtdata_t **dp) { dns_dtdata_t *d; REQUIRE(dp != NULL && *dp != NULL); d = *dp; *dp = NULL; if (d->msg != NULL) { dns_message_detach(&d->msg); } if (d->frame != NULL) { dnstap__dnstap__free_unpacked(d->frame, NULL); } isc_mem_putanddetach(&d->mctx, d, sizeof(*d)); }