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