From 94942ca5d1e549b2491e9774fae6024bb91b55dc Mon Sep 17 00:00:00 2001 From: Robert Edmonds <edmonds@fsi.io> Date: Tue, 22 Apr 2014 17:26:27 -0400 Subject: [PATCH] Add initial dnstap implementation --- .gitignore | 6 + configure.ac | 1 + src/Makefile.am | 2 +- src/dnstap/Makefile.am | 41 ++++++ src/dnstap/dnstap.c | 39 ++++++ src/dnstap/dnstap.h | 54 ++++++++ src/dnstap/dnstap.proto | 268 ++++++++++++++++++++++++++++++++++++++++ src/dnstap/message.c | 124 +++++++++++++++++++ src/dnstap/message.h | 75 +++++++++++ src/dnstap/writer.c | 175 ++++++++++++++++++++++++++ src/dnstap/writer.h | 89 +++++++++++++ 11 files changed, 873 insertions(+), 1 deletion(-) create mode 100644 src/dnstap/Makefile.am create mode 100644 src/dnstap/dnstap.c create mode 100644 src/dnstap/dnstap.h create mode 100644 src/dnstap/dnstap.proto create mode 100644 src/dnstap/message.c create mode 100644 src/dnstap/message.h create mode 100644 src/dnstap/writer.c create mode 100644 src/dnstap/writer.h diff --git a/.gitignore b/.gitignore index d25da82404..82c697bccc 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,12 @@ /src/knot/conf/libknotd_la-cf-parse.h /src/zscanner/scanner.c +# dnstap +/src/dnstap/Makefile +/src/dnstap/Makefile.in +/src/dnstap/dnstap.pb-c.c +/src/dnstap/dnstap.pb-c.h + # zscanner /src/zscanner/tests/tmp/ /src/zscanner/tests/unittests diff --git a/configure.ac b/configure.ac index b0de535cf4..9fc33e86f0 100644 --- a/configure.ac +++ b/configure.ac @@ -384,6 +384,7 @@ AC_CONFIG_FILES([Makefile libtap/Makefile src/Makefile tests/Makefile + src/dnstap/Makefile src/zscanner/Makefile man/khost.1 man/knotc.8 diff --git a/src/Makefile.am b/src/Makefile.am index 074e1c347b..460b93842c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ ACLOCAL_AMFLAGS = -I $(top_srcdir)/m4 -SUBDIRS = zscanner . +SUBDIRS = zscanner dnstap . sbin_PROGRAMS = knotc knotd bin_PROGRAMS = kdig khost knsupdate knsec3hash diff --git a/src/dnstap/Makefile.am b/src/dnstap/Makefile.am new file mode 100644 index 0000000000..f305b353c8 --- /dev/null +++ b/src/dnstap/Makefile.am @@ -0,0 +1,41 @@ +ACLOCAL_AMFLAGS = -I $(top_srcdir)/m4 +AM_CPPFLAGS = \ + -include $(top_builddir)/src/config.h \ + -I$(top_srcdir)/src + +EXTRA_DIST = \ + dnstap.proto + +if HAVE_DNSTAP + +SUFFIXES = .proto .pb-c.c .pb-c.h + +.proto.pb-c.c: + $(AM_V_GEN)@PROTOC_C@ --c_out=. -I$(srcdir) $< + +.proto.pb-c.h: + $(AM_V_GEN)@PROTOC_C@ --c_out=. -I$(srcdir) $< + +noinst_LTLIBRARIES = libdnstap.la + +libdnstap_la_CFLAGS = \ + $(CODE_COVERAGE_CFLAGS) \ + $(DNSTAP_CFLAGS) + +libdnstap_la_LDFLAGS = \ + $(CODE_COVERAGE_LDFLAGS) \ + $(DNSTAP_LIBS) + +libdnstap_la_SOURCES = \ + dnstap.c \ + message.c \ + writer.c + +nodist_libdnstap_la_SOURCES = \ + dnstap.pb-c.c \ + dnstap.pb-c.h + +BUILT_SOURCES = $(nodist_libdnstap_la_SOURCES) +CLEANFILES = $(nodist_libdnstap_la_SOURCES) + +endif diff --git a/src/dnstap/dnstap.c b/src/dnstap/dnstap.c new file mode 100644 index 0000000000..7be147696b --- /dev/null +++ b/src/dnstap/dnstap.c @@ -0,0 +1,39 @@ +/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <stdlib.h> + +#include "dnstap/dnstap.pb-c.h" + +#define DNSTAP_INITIAL_BUF_SIZE 256 + +uint8_t* dt_pack(const Dnstap__Dnstap *d, uint8_t **buf, size_t *sz) +{ + ProtobufCBufferSimple sbuf; + + 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 = 1; + + *sz = dnstap__dnstap__pack_to_buffer(d, (ProtobufCBuffer *) &sbuf); + *buf = sbuf.data; + return *buf; +} diff --git a/src/dnstap/dnstap.h b/src/dnstap/dnstap.h new file mode 100644 index 0000000000..a08c341153 --- /dev/null +++ b/src/dnstap/dnstap.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com> + + 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file dnstap.h + * + * \author Robert Edmonds <edmonds@fsi.io> + * + * \brief Public interface for dnstap. + * @{ + */ + +#ifndef _DNSTAP__DNSTAP_H_ +#define _DNSTAP__DNSTAP_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "dnstap/dnstap.pb-c.h" + +/*! \brief Frame Streams "Content Type" value for dnstap. */ +#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap" + +/*! + * \brief Serializes a filled out dnstap protobuf struct. Dynamically allocates + * storage for the serialized frame. + * + * \note This function returns a copy of its parameter return value 'buf' to + * make error checking slightly easier. + * + * \param d dnstap protobuf struct. + * \param[out] buf Serialized frame. + * \param[out] sz Size in bytes of the serialized frame. + * + * \return Serialized frame. + * \retval NULL if error. + */ +uint8_t* dt_pack(const Dnstap__Dnstap *d, uint8_t **buf, size_t *sz); + +#endif // _DNSTAP__DNSTAP_H_ + +/*! @} */ diff --git a/src/dnstap/dnstap.proto b/src/dnstap/dnstap.proto new file mode 100644 index 0000000000..1ed1bb00e2 --- /dev/null +++ b/src/dnstap/dnstap.proto @@ -0,0 +1,268 @@ +// 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. +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this file to the public +// domain worldwide. This file is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication along +// with this file. If not, see: +// +// <http://creativecommons.org/publicdomain/zero/1.0/>. + +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 transport protocol of a socket. This specifies how to +// interpret "transport port" fields. +enum SocketProtocol { + UDP = 1; // User Datagram Protocol (RFC 768) + TCP = 2; // Transmission Control Protocol (RFC 793) +} + +// 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 authorative + // 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; + } + + // 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/src/dnstap/message.c b/src/dnstap/message.c new file mode 100644 index 0000000000..4d63c61ab7 --- /dev/null +++ b/src/dnstap/message.c @@ -0,0 +1,124 @@ +/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <netinet/in.h> // sockaddr_in +#include <stdint.h> +#include <stdlib.h> +#include <string.h> // memset + +#include "common/errcode.h" + +#include "dnstap/message.h" + +int dt_message_fill(Dnstap__Message *m, + const Dnstap__Message__Type type, + const struct sockaddr *response_sa, + const int protocol, + const void *wire, + const size_t len_wire, + const struct timeval *qtime, + const struct timeval *rtime) +{ + memset(m, 0, sizeof(*m)); + m->base.descriptor = &dnstap__message__descriptor; + + if (type != DNSTAP__MESSAGE__TYPE__TOOL_QUERY && + type != DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE) + { + return KNOT_EINVAL; + } + + // Message.type + m->type = type; + + if (response_sa->sa_family == AF_INET) { + const struct sockaddr_in *sai = + (const struct sockaddr_in *)response_sa; + + // Message.socket_family + m->has_socket_family = 1; + m->socket_family = DNSTAP__SOCKET_FAMILY__INET; + + // Message.response_address + m->response_address.len = 4; + m->response_address.data = (uint8_t *) + &sai->sin_addr.s_addr; + m->has_response_address = 1; + + // Message.response_port + m->has_response_port = 1; + m->response_port = ntohs(sai->sin_port); + } else if (response_sa->sa_family == AF_INET6) { + const struct sockaddr_in6 *sai6 = + (const struct sockaddr_in6 *)response_sa; + + // Message.socket_family + m->socket_family = DNSTAP__SOCKET_FAMILY__INET6; + m->has_socket_family = 1; + + // Message.response_address + m->response_address.len = 16; + m->response_address.data = (uint8_t *) + &sai6->sin6_addr.s6_addr; + m->has_response_address = 1; + + // Message.response_port + m->has_response_port = 1; + m->response_port = ntohs(sai6->sin6_port); + } else { + return KNOT_EINVAL; + } + + // Message.socket_protocol + if (protocol == IPPROTO_UDP) { + m->socket_protocol = DNSTAP__SOCKET_PROTOCOL__UDP; + } else if (protocol == IPPROTO_TCP) { + m->socket_protocol = DNSTAP__SOCKET_PROTOCOL__TCP; + } else { + return KNOT_EINVAL; + } + m->has_socket_protocol = 1; + + if (type == DNSTAP__MESSAGE__TYPE__TOOL_QUERY) { + // Message.query_message + m->query_message.len = len_wire; + m->query_message.data = (uint8_t *)wire; + m->has_query_message = 1; + } else if (type == DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE) { + // Message.response_message + m->response_message.len = len_wire; + m->response_message.data = (uint8_t *)wire; + m->has_response_message = 1; + } + + // Message.query_time_sec, Message.query_time_nsec + if (qtime != NULL) { + m->query_time_sec = qtime->tv_sec; + m->query_time_nsec = qtime->tv_usec * 1000; + m->has_query_time_sec = 1; + m->has_query_time_nsec = 1; + } + + // Message.response_time_sec, Message.response_time_nsec + if (rtime != NULL) { + m->response_time_sec = rtime->tv_sec; + m->response_time_nsec = rtime->tv_usec * 1000; + m->has_response_time_sec = 1; + m->has_response_time_nsec = 1; + } + + return KNOT_EOK; +} diff --git a/src/dnstap/message.h b/src/dnstap/message.h new file mode 100644 index 0000000000..c586a627e2 --- /dev/null +++ b/src/dnstap/message.h @@ -0,0 +1,75 @@ +/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com> + + 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file message.h + * + * \author Robert Edmonds <edmonds@fsi.io> + * + * \brief dnstap message interface. + * + * \addtogroup dnstap + * @{ + */ + +#ifndef _DNSTAP__MESSAGE_H_ +#define _DNSTAP__MESSAGE_H_ + +#include <sys/socket.h> // struct sockaddr +#include <sys/time.h> // struct timeval +#include <stddef.h> // size_t + +#include "dnstap/dnstap.pb-c.h" + +/*! + * \brief Fill a Dnstap__Message structure with the given parameters. + * + * Supported message types: + * \c DNSTAP__MESSAGE__TYPE__TOOL_QUERY + * \c DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE + * + * \param[out] m + * Dnstap__Message structure to fill. Will be zeroed first. + * \param type + * One of the supported message type values. + * \param response_sa + * sockaddr_in or sockaddr_in6 to use when filling the 'socket_family', + * 'response_address', 'response_port' fields. + * \param protocol + * \c IPPROTO_UDP or \c IPPROTO_TCP. + * \param wire + * Wire-format query message or response message (depending on 'type'). + * \param len_wire + * Length in bytes of 'wire'. + * \param qtime + * Query time. May be NULL. + * \param rtime + * Response time. May be NULL. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + */ +int dt_message_fill(Dnstap__Message *m, + const Dnstap__Message__Type type, + const struct sockaddr *response_sa, + const int protocol, + const void *wire, + const size_t len_wire, + const struct timeval *qtime, + const struct timeval *rtime); + +#endif // _DNSTAP__MESSAGE_H_ + +/*! @} */ diff --git a/src/dnstap/writer.c b/src/dnstap/writer.c new file mode 100644 index 0000000000..fa7c4cf81d --- /dev/null +++ b/src/dnstap/writer.c @@ -0,0 +1,175 @@ +/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> // htonl +#include <errno.h> +#include <stdint.h> // uint8_t, uint32_t +#include <stdio.h> // fopen, fwrite +#include <stdlib.h> // calloc, free +#include <string.h> // strdup + +#include "common/errcode.h" +#include "libknot/common.h" + +#include "dnstap/dnstap.pb-c.h" +#include "dnstap/dnstap.h" +#include "dnstap/writer.h" + +#define DNSTAP_INITIAL_BUF_SIZE 256 + +static int dt_writer_write_control(dt_writer_t *writer, + fstrm_control_type type) +{ + fstrm_res res; + + // Encode the control frame. + res = fstrm_control_set_type(writer->control, type); + if (res != fstrm_res_success) { + return KNOT_ERROR; + } + + // Write the control frame. + if (writer->fp != NULL) { + uint8_t frame[FSTRM_MAX_CONTROL_FRAME_LENGTH]; + size_t len = sizeof(frame); + + res = fstrm_control_encode(writer->control, frame, &len, + FSTRM_CONTROL_FLAG_WITH_HEADER); + if (res != fstrm_res_success) { + return KNOT_ERROR; + } + fwrite(frame, len, 1, writer->fp); + } + + return KNOT_EOK; +} + +dt_writer_t* dt_writer_create(const char *file_name, const char *version) +{ + dt_writer_t *writer = NULL; + fstrm_res res; + + writer = calloc(1, sizeof(dt_writer_t)); + if (writer == NULL) { + goto fail; + } + + // Set "version". + if (version != NULL) { + writer->len_version = strlen(version); + writer->version = (uint8_t *)strdup(version); + if (!writer->version) { + goto fail; + } + } + + // Open file. + writer->fp = fopen(file_name, "w"); + if (writer->fp == NULL) { + goto fail; + } + + // Initialize the control frame object. + writer->control = fstrm_control_init(); + res = fstrm_control_set_field_content_type(writer->control, + (const uint8_t *) DNSTAP_CONTENT_TYPE, + strlen(DNSTAP_CONTENT_TYPE)); + if (res != fstrm_res_success) { + goto fail; + } + + // Write the START control frame. + if (dt_writer_write_control(writer, FSTRM_CONTROL_START) != KNOT_EOK) { + goto fail; + } + + return writer; +fail: + dt_writer_free(writer); + return NULL; +} + +int dt_writer_close(dt_writer_t *writer) +{ + FILE *fp; + int rv = KNOT_EOK; + + // Write the STOP control frame. + if (writer->fp != NULL) { + rv = dt_writer_write_control(writer, FSTRM_CONTROL_STOP); + } + + // Close file. + fp = writer->fp; + writer->fp = NULL; + if (fp != NULL) { + if (fclose(fp) != 0) { + return knot_map_errno(errno); + } + } + + return rv; +} + +int dt_writer_free(dt_writer_t *writer) +{ + int rv = KNOT_EOK; + if (writer != NULL) { + rv = dt_writer_close(writer); + fstrm_control_destroy(&writer->control); + free(writer->version); + free(writer); + } + return rv; +} + +int dt_writer_write(dt_writer_t *writer, const ProtobufCMessage *msg) +{ + uint32_t be_len; + size_t len; + uint8_t *data; + Dnstap__Dnstap dnstap = DNSTAP__DNSTAP__INIT; + + if (writer->fp == NULL) + return KNOT_EOK; + + // Only handle dnstap/Message. + if (knot_unlikely(msg->descriptor != &dnstap__message__descriptor)) + return KNOT_EINVAL; + + // Fill out 'dnstap'. + if (writer->version) { + dnstap.version.data = writer->version; + dnstap.version.len = writer->len_version; + dnstap.has_version = 1; + } + dnstap.type = DNSTAP__DNSTAP__TYPE__MESSAGE; + dnstap.message = (Dnstap__Message *)msg; + + // Serialize the dnstap frame. + if (!dt_pack(&dnstap, &data, &len)) + return KNOT_ENOMEM; + + // Write the dnstap frame to the output stream. + be_len = htonl(len); + fwrite(&be_len, sizeof(be_len), 1, writer->fp); + fwrite(data, len, 1, writer->fp); + + // Cleanup. + free(data); + + return KNOT_EOK; +} diff --git a/src/dnstap/writer.h b/src/dnstap/writer.h new file mode 100644 index 0000000000..8554d482d2 --- /dev/null +++ b/src/dnstap/writer.h @@ -0,0 +1,89 @@ +/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com> + + 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file writer.h + * + * \author Robert Edmonds <edmonds@fsi.io> + * + * \brief dnstap file writer. + * + * \addtogroup dnstap + * @{ + */ + +#ifndef _DNSTAP__WRITER_H_ +#define _DNSTAP__WRITER_H_ + +#include <stdio.h> // FILE +#include <stdint.h> // uint8_t + +#include <fstrm.h> // fstrm_control +#include <protobuf-c/protobuf-c.h> // ProtobufCMessage + +/*! \brief Structure for dnstap file writer. */ +typedef struct { + /*!< Output FILE. */ + FILE *fp; + + /*!< dnstap "version" field. */ + uint8_t *version; + + /*!< length of dnstap "version" field. */ + size_t len_version; + + /*!< Frame Streams control frame structure. */ + struct fstrm_control *control; +} dt_writer_t; + +/*! + * \brief Creates dnstap file writer structure. + * + * \param file_name Name of file to write output to. + * \param version Version string of software. May be NULL. + * + * \retval writer if success. + * \retval NULL if error. + */ +dt_writer_t* dt_writer_create(const char *file_name, const char *version); + +/*! + * \brief Finish writing dnstap file writer and free resources. + * + * \param writer dnstap file writer structure. + * + * \retval KNOT_EOK + * \retval errcode if error. + */ +int dt_writer_free(dt_writer_t *writer); + +/*! + * \brief Write a protobuf to the dnstap file writer. + * + * Supported protobuf types for the 'msg' parameter: + * \c Dnstap__Message + * + * \param writer dnstap file writer structure. + * \param msg dnstap protobuf. Must be a supported type. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + * \retval KNOT_ENOMEM + */ +int dt_writer_write(dt_writer_t *writer, const ProtobufCMessage *msg); + +#endif // _DNSTAP__DNSTAP_H_ + +/*! @} */ -- GitLab