diff --git a/.travis.yml b/.travis.yml
index fccc978732e455ab50b079821c2eb3af265ec12b..1800407d7a66b025d33eb2b273b3ef954177fbcf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,12 +2,13 @@ language: c
 compiler:
   - gcc
 before_install:
+  - sudo add-apt-repository --yes ppa:cz.nic-labs/knot-dns
   - sudo apt-get update -qq
-  - sudo apt-get install -qq autotools-dev autoconf automake libtool libssl-dev liburcu-dev flex bison ragel pkg-config texinfo texlive lcov
+  - sudo apt-get install -qq autotools-dev autoconf automake libtool libssl-dev liburcu-dev flex bison ragel pkg-config texinfo texlive lcov liblmdb-dev
   - sudo pip install cpp-coveralls --use-mirrors
 before_script:
   - autoreconf -fi
 script:
   - ./configure --disable-fastparser --disable-shared --enable-static --enable-code-coverage && make && make -k check
 after_success:
-  - coveralls --exclude tests/ --exclude src/cf-lex.l --exclude src/cf-parse.y --exclude ./src/utils/ --exclude ./src/libtap --exclude ./src/zscanner --build-root ./src/
\ No newline at end of file
+  - coveralls --exclude tests/ --exclude src/cf-lex.l --exclude src/cf-parse.y --exclude ./src/utils/ --exclude ./src/libtap --exclude ./src/zscanner --build-root ./src/
diff --git a/Knot.files b/Knot.files
index a63151344551ee9364bdd6533b0b2e66cdb711f2..2583937bd5659858bb0466d9bcb12767a8bbf68f 100644
--- a/Knot.files
+++ b/Knot.files
@@ -74,8 +74,13 @@ src/knot/dnssec/zone-nsec.h
 src/knot/dnssec/zone-sign.c
 src/knot/dnssec/zone-sign.h
 src/knot/main.c
+src/knot/modules/dnsproxy.c
+src/knot/modules/dnsproxy.h
 src/knot/modules/dnstap.c
 src/knot/modules/dnstap.h
+src/knot/modules/rosedb.c
+src/knot/modules/rosedb.h
+src/knot/modules/rosedb_tool.c
 src/knot/modules/synth_record.c
 src/knot/modules/synth_record.h
 src/knot/nameserver/axfr.c
diff --git a/configure.ac b/configure.ac
index 93f160516eb2f8f87424723c0b29fd256034afa4..f777d9f9997d47a0ae37f43588f53845f527e706 100644
--- a/configure.ac
+++ b/configure.ac
@@ -314,6 +314,14 @@ AM_CONDITIONAL([HAVE_DNSTAP], test "$opt_dnstap" != "no")
 dnl Check for LMDB
 KNOT_CHECK_HEADER([lmdb], [LMDB], [auto], [lmdb.h], [], [-llmdb])
 AS_IF([test "$enable_lmdb" = yes], [AC_DEFINE([HAVE_LMDB], [1], [Define to 1 to enable LMDB support])])
+AM_CONDITIONAL([HAVE_LMDB], test "$enable_lmdb" != "no")
+
+dnl Check for rosedb module 
+AC_ARG_ENABLE([rosedb],
+    AS_HELP_STRING([--disable-rosedb], [Do not enable static RR query module.]),
+    [], [enable_rosedb=no])
+AM_CONDITIONAL([HAVE_ROSEDB], [test "$enable_rosedb" = yes])
+AS_IF([test "$enable_rosedb" = yes], [AC_DEFINE([HAVE_ROSEDB], [1], [Define to 1 to enable static RR query module.])])
 
 AC_SEARCH_LIBS([pow], [m])
 AC_SEARCH_LIBS([pthread_create], [pthread], [], [AC_MSG_ERROR([pthreads not found])])
diff --git a/contrib/knot.conf.rosedb b/contrib/knot.conf.rosedb
new file mode 100644
index 0000000000000000000000000000000000000000..787a9c101c05c2014f5d142e85a8bae2d50e01c4
--- /dev/null
+++ b/contrib/knot.conf.rosedb
@@ -0,0 +1,58 @@
+#
+# This is a sample of a minimal configuration file for Knot DNS when used with RoseDB.
+
+system {
+  # Identity of the server (see RFC 4892).
+  identity on;
+
+  # Version of the server (see RFC 4892)
+  version on;
+
+  # User for running server
+  # May also specify user.group (e.g. knot.knot)
+  user knot.knot;
+}
+
+interfaces {
+  all_ipv4 {
+    address 0.0.0.0;
+    port 53;
+  }
+  all_ipv6 {
+    address [::];
+    port 53;
+  }
+}
+
+control {
+  # Default: knot.sock (relative to rundir)
+  listen-on "knot.sock";
+}
+
+zones {
+  query_module { 
+    rosedb "/etc/knot/rosedb";
+    # catchall dnsproxy
+    # dnsproxy "192.0.2.1";
+  }
+
+# dnsproxy for specific domain name
+#
+# example.com {
+#   query_module {
+#     dnsproxy "198.51.100.1";
+#   }
+# }
+}
+
+log {
+  syslog {
+    # Log info and more serious events to syslog
+    any info;
+  }
+
+  # Log warnings, errors and criticals to stderr
+  stderr {
+    any warning;
+  }
+}
diff --git a/doc/configuration.rst b/doc/configuration.rst
index b2fe0da8d15b06f3547d3d4c81a68fbac2a67368..4c0f80973d506273b42fc1921074190dcbcb10dd 100644
--- a/doc/configuration.rst
+++ b/doc/configuration.rst
@@ -469,3 +469,118 @@ Limitations
   NSEC or NSEC3 is supported) nor DNSSEC signed records.  However,
   since the module is hooked in the query processing plan, it will be
   possible to do online signing in the future.
+
+``dnsproxy`` - Tiny DNS proxy 
+-----------------------------
+
+The module catches all unsatisfied queries and forwards them to the configured server for resolution,
+i.e. a tiny DNS proxy. This can be useful to several things:
+
+* A substitute public-facing server in front of the real one
+* Local zones (poor man's "views"), rest is forwarded to the public-facing server
+* etc.
+
+The configuration is straightforward and just accepts a single IP address (either IPv4 or IPv6).
+
+*Note: The module does not alter the query/response as the resolver would do, also the original
+transport protocol is kept.*
+
+Example
+^^^^^^^
+
+Example configuration::
+
+        $ vim knot.conf
+        knot.conf:
+        zones {
+                local.zone {}
+                query_module {
+                        dnsproxy "10.0.1.1";
+                }
+        }
+
+Now when the clients query for anything in the ``local.zone``, it will be answered locally.
+Rest of the requests will be forwarded to the specified server (``10.0.1.1`` in this case).
+
+``rosedb`` - Static resource records
+------------------------------------
+
+The module provides a mean to override responses for certain queries before the record is searched in
+the available zones. The modules comes with a tool ``rosedb_tool`` to manipulate with the database
+of static records. Neither the tool nor the module are enabled by default, recompile with the configure flag ``--enable-rosedb``
+to enable them.
+
+For example, suppose we have a database of following records::
+
+        myrecord.com.      3600 IN A 127.0.0.1
+        www.myrecord.com.  3600 IN A 127.0.0.2
+        ipv6.myrecord.com. 3600 IN AAAA ::1
+
+And we query the nameserver with following::
+
+        $ kdig IN A myrecord.com
+          ... returns NOERROR, 127.0.0.1
+        $ kdig IN A www.myrecord.com
+          ... returns NOERROR, 127.0.0.2
+        $ kdig IN A stuff.myrecord.com
+          ... returns NOERROR, 127.0.0.1
+        $ kdig IN AAAA myrecord.com
+          ... returns NOERROR, NODATA
+        $ kdig IN AAAA ipv6.myrecord.com
+          ... returns NOERROR, ::1 
+
+*Note: An entry in the database matches anything at or below it, i.e. 'myrecord.com' matches 'a.a.myrecord.com' as well.
+This can be exploited to create a catch-all entries.*
+
+You can also add an authority information for the entries, provided you create a SOA + NS records for a name, like so::
+
+        myrecord.com.     3600 IN SOA master host 1 3600 60 3600 3600
+        myrecord.com.     3600 IN NS ns1.myrecord.com.
+        myrecord.com.     3600 IN NS ns2.myrecord.com.
+        ns1.myrecord.com. 3600 IN A 127.0.0.1
+        ns2.myrecord.com. 3600 IN A 127.0.0.2
+
+In this case, the responses will:
+
+1. Be authoritative (AA flag set)
+2. Provide an authority section (SOA + NS)
+3. NXDOMAIN if the name is found *(i.e. the 'IN AAAA myrecord.com' from the example)*, but not the RR type *(this is to allow synthesis of negative responses)*
+
+*Note: The SOA record applies only to the 'myrecord.com.', not to any other record (even below it). From this point of view,
+all records in the database are unrelated and not hierarchical. The reasoning is to provide a subtree isolation for each entry.*
+        
+In addition the module is able to log matching queries via remote syslog if you specify a syslog address endpoint and an
+optional string code.
+
+Here is an example on how to use the module:
+
+* Create the entries in the database::
+
+        $ mkdir /tmp/static_rrdb 
+        $ rosedb_tool /tmp/static_rrdb add myrecord.com. A 3600 "127.0.0.1" "-" "-" # No logging
+        $ rosedb_tool /tmp/static_rrdb add www.myrecord.com. A 3600 "127.0.0.1" "www_query" "10.0.0.1" # Syslog @ 10.0.0.1 
+        $ rosedb_tool /tmp/static_rrdb add ipv6.myrecord.com. AAAA 3600 "::1" "ipv6_query" "10.0.0.1" # Syslog @ 10.0.0.1 
+        $ rosedb_tool /tmp/static_rrdb list # Verify
+        www.myrecord.com.       A RDATA=10B     www_query       10.0.0.1
+        ipv6.myrecord.com.      AAAA RDATA=22B  ipv6_query      10.0.0.1
+        myrecord.com.           A RDATA=10B     -               -
+
+ *Note: the database may be modified while the server is running later on.*
+
+* Configure the query module and start the server::
+
+        $ vim knot.conf
+        knot.conf:
+        zones {
+                query_module {
+                        rosedb "/tmp/static_rrdb";
+                }
+        }
+
+        $ knotd -c knot.conf
+
+  *Note: The module accepts just one parameter - path to the directory where the database will be stored.*
+
+* Verify the running instance::
+
+        $ kdig @127.0.0.1#6667 A myrecord.com
diff --git a/scripts/Dockerfile b/scripts/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..64f554562e07a1292539ddbfdb9a2c153f5c6868
--- /dev/null
+++ b/scripts/Dockerfile
@@ -0,0 +1,34 @@
+FROM debian:jessie 
+MAINTAINER Marek Vavrusa <marek.vavrusa@nic.cz>
+
+# Nprocs
+ENV THREADS 2
+
+# Install dependencies and sources 
+RUN apt-get -y update; \
+apt-get install -y \
+build-essential \
+git-core \
+libtool \
+autoconf \
+flex \
+bison \
+libssl-dev \
+liburcu-dev \
+pkg-config \
+liblmdb-dev; \
+# Fetch sources
+mkdir /src; \
+git clone https://gitlab.labs.nic.cz/labs/knot.git /src/knot; \
+# Compile sources in the right order
+cd /src/knot && autoreconf -if && ./configure && make -j${THREADS} && make install && ldconfig; \
+# Trim down the image
+apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /src
+
+# Select entrypoint
+WORKDIR /root
+ENTRYPOINT "/usr/local/sbin/knotd"
+CMD ["--help"]
+
+# Expose port
+EXPOSE 53
diff --git a/src/Makefile.am b/src/Makefile.am
index b9930202a40c9fdeb2ec939fb749a03ed7391d7d..798f0e9e95b90e1ec7377651463c0d03485b5740 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -233,6 +233,8 @@ libknotd_la_SOURCES =				\
 	knot/dnssec/zone-sign.h			\
 	knot/modules/synth_record.c		\
 	knot/modules/synth_record.h		\
+	knot/modules/dnsproxy.c		\
+	knot/modules/dnsproxy.h		\
 	knot/nameserver/axfr.c			\
 	knot/nameserver/axfr.h			\
 	knot/nameserver/capture.c		\
@@ -368,6 +370,19 @@ khost_LDADD        += dnstap/libdnstap.la
 libknotd_la_LIBADD += dnstap/libdnstap.la
 endif
 
+# rose sources and client tool
+if HAVE_ROSEDB
+libknotd_la_SOURCES +=				\
+	knot/modules/rosedb.c				\
+	knot/modules/rosedb.h
+
+rosedb_tool_SOURCES =					\
+	knot/modules/rosedb_tool.c
+
+bin_PROGRAMS += rosedb_tool
+rosedb_tool_LDADD = libknot.la libknotd.la
+endif
+
 # pkg-config
 pkgconfig_DATA = $(top_srcdir)/libknot.pc
 
diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c
index 2707ff28943fc8b1e5e045e1062b70c78bc51d70..238045bbcc05900b37bf989d0e96f4fb5db8e321 100644
--- a/src/knot/conf/conf.c
+++ b/src/knot/conf/conf.c
@@ -226,7 +226,7 @@ static int conf_process(conf_t *conf)
 		if (ctl_if->addr.ss_family == AF_UNIX) {
 			char *full_path = malloc(SOCKADDR_STRLEN);
 			memset(full_path, 0, SOCKADDR_STRLEN);
-			sockaddr_tostr(&ctl_if->addr, full_path, SOCKADDR_STRLEN);
+			sockaddr_tostr(full_path, SOCKADDR_STRLEN, &ctl_if->addr);
 
 			/* Convert to absolute path. */
 			full_path = conf_abs_path(conf->rundir, full_path);
diff --git a/src/knot/ctl/knotc_main.c b/src/knot/ctl/knotc_main.c
index 02d247aa78151513855fdbf75cfd4ae07ccc6d0a..cf8bc1b425e80173463184b998930485ae95cc04 100644
--- a/src/knot/ctl/knotc_main.c
+++ b/src/knot/ctl/knotc_main.c
@@ -240,7 +240,7 @@ static int cmd_remote(const char *cmd, uint16_t rrt, int argc, char *argv[])
 
 	/* Connect to remote. */
 	char addr_str[SOCKADDR_STRLEN] = {0};
-	sockaddr_tostr(&r->addr, addr_str, sizeof(addr_str));
+	sockaddr_tostr(addr_str, sizeof(addr_str), &r->addr);
 
 	int s = net_connected_socket(SOCK_STREAM, &r->addr, &r->via, 0);
 	if (s < 0) {
diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c
index 9314015acbc46587075814a59eac2589e39091c6..3c48bd1a176ef92ac3764cad9afef3570d62295f 100644
--- a/src/knot/ctl/remote.c
+++ b/src/knot/ctl/remote.c
@@ -501,7 +501,7 @@ int remote_bind(conf_iface_t *desc)
 	}
 
 	char addr_str[SOCKADDR_STRLEN] = {0};
-	sockaddr_tostr(&desc->addr, addr_str, sizeof(addr_str));
+	sockaddr_tostr(addr_str, sizeof(addr_str), &desc->addr);
 	log_info("binding remote control interface to '%s'", addr_str);
 
 	/* Create new socket. */
@@ -532,7 +532,7 @@ int remote_unbind(conf_iface_t *desc, int sock)
 	/* Remove control socket file.  */
 	if (desc->addr.ss_family == AF_UNIX) {
 		char addr_str[SOCKADDR_STRLEN] = {0};
-		sockaddr_tostr(&desc->addr, addr_str, sizeof(addr_str));
+		sockaddr_tostr(addr_str, sizeof(addr_str), &desc->addr);
 		unlink(addr_str);
 	}
 
@@ -870,7 +870,7 @@ int remote_process(server_t *s, conf_iface_t *ctl_if, int sock,
 
 		/* Check ACL list. */
 		char addr_str[SOCKADDR_STRLEN] = {0};
-		sockaddr_tostr(&ss, addr_str, sizeof(addr_str));
+		sockaddr_tostr(addr_str, sizeof(addr_str), &ss);
 		knot_tsig_key_t *tsig_key = NULL;
 		const knot_dname_t *tsig_name = NULL;
 		if (pkt->tsig_rr) {
diff --git a/src/knot/modules/dnsproxy.c b/src/knot/modules/dnsproxy.c
new file mode 100644
index 0000000000000000000000000000000000000000..a03460d8e8b7cc5921d5294a6d5e57606999812e
--- /dev/null
+++ b/src/knot/modules/dnsproxy.c
@@ -0,0 +1,108 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 "libknot/processing/requestor.h"
+#include "knot/modules/dnsproxy.h"
+#include "knot/nameserver/capture.h"
+#include "knot/nameserver/process_query.h"
+
+#define MODULE_ERR(msg...) log_error("module 'dnsproxy', " msg)
+
+struct dnsproxy {
+	conf_iface_t remote;
+};
+
+static int dnsproxy_fwd(int state, knot_pkt_t *pkt, struct query_data *qdata, void *ctx)
+{
+	if (pkt == NULL || qdata == NULL || ctx == NULL) {
+		return KNOT_NS_PROC_FAIL;
+	}
+
+	/* If not already satisfied. */
+	if (state == KNOT_NS_PROC_DONE) {
+		return state;
+	}
+
+	struct dnsproxy *proxy = ctx;
+
+	/* Create a forwarding request. */
+	struct knot_requestor re;
+	knot_requestor_init(&re, qdata->mm);
+	struct capture_param param;
+	param.sink = pkt;
+	int ret = knot_requestor_overlay(&re, LAYER_CAPTURE, &param);
+	if (ret != KNOT_EOK) {
+		return KNOT_NS_PROC_FAIL;
+	}
+
+	bool is_tcp = net_is_connected(qdata->param->socket);
+	struct knot_request *req = knot_request_make(re.mm, (struct sockaddr *)&proxy->remote.addr,
+	                                             NULL, qdata->query, is_tcp ? 0 : KNOT_RQ_UDP);
+	if (req == NULL) {
+		return state; /* Ignore, not enough memory. */
+	}
+
+	/* Forward request. */
+	ret = knot_requestor_enqueue(&re, req);
+	if (ret == KNOT_EOK) {
+		struct timeval tv = { conf()->max_conn_hs, 0 };
+		ret = knot_requestor_exec(&re, &tv);
+	} else {
+		knot_request_free(re.mm, req);
+	}
+
+	knot_requestor_clear(&re);
+
+	/* Check result. */
+	if (ret != KNOT_EOK) {
+		qdata->rcode = KNOT_RCODE_SERVFAIL;
+		return KNOT_NS_PROC_FAIL; /* Forwarding failed, SERVFAIL. */
+	}
+
+	return KNOT_NS_PROC_DONE;
+}
+
+int dnsproxy_load(struct query_plan *plan, struct query_module *self)
+{
+	struct dnsproxy *proxy = mm_alloc(self->mm, sizeof(struct dnsproxy));
+	if (proxy == NULL) {
+		MODULE_ERR("not enough memory");
+		return KNOT_ENOMEM;
+	}
+	memset(proxy, 0, sizeof(struct dnsproxy));
+
+	/* Determine IPv4/IPv6 */
+	int family = AF_INET;
+	if (strchr(self->param, ':')) {
+		family = AF_INET6;
+	}
+
+	int ret = sockaddr_set(&proxy->remote.addr, family, self->param, 53);
+	if (ret != KNOT_EOK) {
+		MODULE_ERR("invalid proxy address: '%s'", self->param);
+		mm_free(self->mm, proxy);
+		return KNOT_EINVAL;
+	}
+
+	return query_plan_step(plan, QPLAN_BEGIN, dnsproxy_fwd, proxy);
+}
+
+int dnsproxy_unload(struct query_module *self)
+{
+	mm_free(self->mm, self->ctx);
+	return KNOT_EOK;
+}
+
diff --git a/src/knot/modules/dnsproxy.h b/src/knot/modules/dnsproxy.h
new file mode 100644
index 0000000000000000000000000000000000000000..48c0af0e6f6524bcb823c710cb65d1ed2d767c49
--- /dev/null
+++ b/src/knot/modules/dnsproxy.h
@@ -0,0 +1,42 @@
+/*!
+ * \file dnsproxy.h
+ *
+ * \author Marek Vavrusa <marek.vavrusa@nic.cz>
+ *
+ * \brief DNS proxy module
+ *
+ * Accepted configurations:
+ *  * "<address>"
+ *
+ * Module forwards all unsatisfied queries to the specified server in
+ * order to solve them, and then sends the response back, i.e. a tiny
+ * DNS proxy.
+ *
+ * \addtogroup query_processing
+ * @{
+ */
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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/>.
+ */
+
+#pragma once
+
+#include "knot/nameserver/query_module.h"
+
+/*! \brief Module interface. */
+int dnsproxy_load(struct query_plan *plan, struct query_module *self);
+int dnsproxy_unload(struct query_module *self);
+
+/*! @} */
diff --git a/src/knot/modules/rosedb.c b/src/knot/modules/rosedb.c
new file mode 100644
index 0000000000000000000000000000000000000000..a5507ead6dc9ea7dbdbce581d745658f6b847949
--- /dev/null
+++ b/src/knot/modules/rosedb.c
@@ -0,0 +1,615 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <lmdb.h>
+
+#include "knot/modules/rosedb.h"
+#include "knot/nameserver/process_query.h"
+#include "libknot/rrtype/rdname.h"
+#include "libknot/dnssec/random.h"
+#include "libknot/rrset-dump.h"
+#include "libknot/internal/utils.h"
+
+/*! \note Below is an implementation of basic RR cache in LMDB,
+ *        it shall be replaced with the namedb API later, when
+ *        it supports multiple dbs + the basic "node" representation,
+ *        as the cache implementation requires DUPSORT.
+ */
+
+#define LMDB_MAPSIZE (100 * 1024 * 1024)
+
+struct cache
+{
+	MDB_dbi dbi;
+	MDB_env *env;
+	mm_ctx_t *pool;
+};
+
+struct rdentry {
+	uint16_t type;
+	knot_rdataset_t rrs;
+};
+
+struct entry {
+	struct rdentry data;
+	const char *threat_code;
+	const char *syslog_ip;
+	MDB_cursor *cursor;
+};
+
+struct iter {
+	MDB_cursor *cur;
+	MDB_val key;
+	MDB_val val;
+};
+
+/*                       MDB access                                           */
+
+static int dbase_open(struct cache *cache, const char *handle)
+{
+	long page_size = sysconf(_SC_PAGESIZE);
+	if (page_size <= 0) {
+		return KNOT_EINVAL;
+	}
+
+	int ret = mdb_env_create(&cache->env);
+	if (ret != 0) {
+		return ret;
+	}
+
+	size_t map_size = (LMDB_MAPSIZE / page_size) * page_size;
+	ret = mdb_env_set_mapsize(cache->env, map_size);
+	if (ret != 0) {
+		mdb_env_close(cache->env);
+		return ret;
+	}
+
+	ret = mdb_env_open(cache->env, handle, 0, 0644);
+	if (ret != 0) {
+		mdb_env_close(cache->env);
+		return ret;
+	}
+
+	MDB_txn *txn = NULL;
+	ret = mdb_txn_begin(cache->env, NULL, 0, &txn);
+	if (ret != 0) {
+		mdb_env_close(cache->env);
+		return ret;
+	}
+
+	ret = mdb_open(txn, NULL, MDB_DUPSORT, &cache->dbi);
+	if (ret != 0) {
+		mdb_txn_abort(txn);
+		mdb_env_close(cache->env);
+		return ret;
+	}
+
+	ret = mdb_txn_commit(txn);
+	if (ret != 0) {
+		mdb_env_close(cache->env);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void dbase_close(struct cache *cache)
+{
+	mdb_close(cache->env, cache->dbi);
+	mdb_env_close(cache->env);
+}
+
+/*                       data access                                          */
+
+static MDB_cursor *cursor_acquire(MDB_txn *txn, MDB_dbi dbi)
+{
+	MDB_cursor *cursor = NULL;
+
+	int ret = mdb_cursor_open(txn, dbi, &cursor);
+	if (ret != 0) {
+		return NULL;
+	}
+
+	return cursor;
+}
+
+static void cursor_release(MDB_cursor *cursor)
+{
+	mdb_cursor_close(cursor);
+}
+
+/*                       data serialization                                   */
+
+#define ENTRY_MAXLEN 65535
+#define PACKED_LEN(str) (strlen(str) + 1) /* length of packed string including terminal byte */
+static inline void pack_str(char **stream, const char *str) {
+	int len = PACKED_LEN(str);
+	memcpy(*stream, str, len);
+	*stream += len;
+}
+
+static inline char *unpack_str(char **stream) {
+	char *ret = *stream;
+	*stream += PACKED_LEN(ret);
+	return ret;
+}
+
+static inline void pack_bin(char **stream, const void *data, uint32_t len) {
+	wire_write_u32((uint8_t *)*stream, len);
+	*stream += sizeof(uint32_t);
+	memcpy(*stream, data, len);
+	*stream += len;
+}
+
+static inline void *unpack_bin(char **stream, uint32_t *len) {
+	*len = wire_read_u32((uint8_t *)*stream);
+	*stream += sizeof(uint32_t);
+	void *ret = *stream;
+	*stream += *len;
+	return ret;
+}
+
+static MDB_val pack_key(const knot_dname_t *name)
+{
+	MDB_val key = { knot_dname_size(name), (void *)name };
+	return key;
+}
+
+static int pack_entry(MDB_val *data, struct entry *entry)
+{
+	char *stream = data->mv_data;
+	char *bptr = stream;
+	pack_bin(&stream, &entry->data.type, sizeof(entry->data.type));
+	knot_rdataset_t *rrs = &entry->data.rrs;
+	pack_bin(&stream, &rrs->rr_count, sizeof(rrs->rr_count));
+	pack_bin(&stream, rrs->data, knot_rdataset_size(rrs));
+
+	pack_str(&stream, entry->threat_code);
+	pack_str(&stream, entry->syslog_ip);
+
+	data->mv_size = (stream - bptr);
+	return KNOT_EOK;
+}
+
+static int unpack_entry(MDB_val *data, struct entry *entry)
+{
+	uint32_t len = 0;
+	void *val = NULL;
+	char *stream = data->mv_data;
+
+	val = unpack_bin(&stream, &len);
+	memcpy(&entry->data.type, val, sizeof(uint16_t));
+
+	knot_rdataset_t *rrs = &entry->data.rrs;
+	val = unpack_bin(&stream, &len);
+	memcpy(&rrs->rr_count, val, sizeof(uint16_t));
+	rrs->data = unpack_bin(&stream, &len);
+
+	entry->threat_code = unpack_str(&stream);
+	entry->syslog_ip = unpack_str(&stream);
+
+	return KNOT_EOK;
+}
+
+static int remove_entry(MDB_cursor *cur)
+{
+	int ret = mdb_cursor_del(cur, MDB_NODUPDATA);
+	if (ret != 0) {
+		return KNOT_ERROR;
+	}
+
+	return KNOT_EOK;
+}
+
+/*                       database api                                   */
+
+struct cache *cache_open(const char *handle, unsigned flags, mm_ctx_t *mm)
+{
+	struct cache *cache = mm_alloc(mm, sizeof(struct cache));
+	if (cache == NULL) {
+		return NULL;
+	}
+	memset(cache, 0, sizeof(struct cache));
+
+	int ret = dbase_open(cache, handle);
+	if (ret != 0) {
+		mm_free(mm, cache);
+		return NULL;
+	}
+
+	cache->pool = mm;
+	return cache;
+}
+
+void cache_close(struct cache *cache)
+{
+	if (cache == NULL) {
+		return;
+	}
+
+	dbase_close(cache);
+	mm_free(cache->pool, cache);
+}
+
+static int cache_iter_begin(struct iter *it, const knot_dname_t *name)
+{
+	it->key = pack_key(name);
+	it->val.mv_data = NULL;
+	it->val.mv_size = 0;
+
+	return mdb_cursor_get(it->cur, &it->key, &it->val, MDB_SET_KEY);
+}
+
+static int cache_iter_next(struct iter *it)
+{
+	return mdb_cursor_get(it->cur, &it->key, &it->val, MDB_NEXT_DUP);
+}
+
+static int cache_iter_val(struct iter *it, struct entry *entry)
+{
+	return unpack_entry(&it->val, entry);
+}
+
+static void cache_iter_free(struct iter *it)
+{
+	mdb_cursor_close(it->cur);
+	it->cur = NULL;
+}
+
+int cache_query_fetch(MDB_txn *txn, MDB_dbi dbi, struct iter *it, const knot_dname_t *name)
+{
+	it->cur = cursor_acquire(txn, dbi);
+	if (it->cur == NULL) {
+		return KNOT_ERROR;
+	}
+
+	int ret = cache_iter_begin(it, name);
+	if (ret != 0) {
+		cache_iter_free(it);
+		return KNOT_ENOENT;
+	}
+
+	return KNOT_EOK;
+}
+
+int cache_insert(MDB_txn *txn, MDB_dbi dbi, const knot_dname_t *name, struct entry *entry)
+{
+	MDB_cursor *cursor = cursor_acquire(txn, dbi);
+	if (cursor == NULL) {
+		return KNOT_ERROR;
+	}
+
+	MDB_val key = pack_key(name);
+	MDB_val data = { 0, malloc(ENTRY_MAXLEN) };
+
+	int ret = pack_entry(&data, entry);
+	if (ret != KNOT_EOK) {
+		free(data.mv_data);
+		return ret;
+	}
+
+	ret = mdb_cursor_put(cursor, &key, &data, 0);
+	free(data.mv_data);
+	cursor_release(cursor);
+
+	return ret;
+}
+
+int cache_remove(MDB_txn *txn, MDB_dbi dbi, const knot_dname_t *name)
+{
+	struct iter it;
+	it.cur = cursor_acquire(txn, dbi);
+	if (it.cur == NULL) {
+		return KNOT_ERROR;
+	}
+
+	int ret = cache_iter_begin(&it, name);
+	if (ret == 0) {
+		ret = remove_entry(it.cur);
+	}
+
+	cursor_release(it.cur);
+	return ret;
+}
+
+/*                       module callbacks                                   */
+
+#define DEFAULT_PORT 514
+#define SYSLOG_BUFLEN 1024 /* RFC3164, 4.1 message size. */
+#define SYSLOG_FACILITY 3  /* System daemon. */
+#define MODULE_ERR(msg...) log_error("module 'rose', " msg)
+
+/*! \brief Safe stream skipping. */
+static int stream_skip(char **stream, size_t *maxlen, int nbytes)
+{
+	/* Error or space limit exceeded. */
+	if (nbytes < 0 || nbytes >= *maxlen) {
+		return KNOT_ESPACE;
+	}
+
+	*stream += nbytes;
+	*maxlen -= nbytes;
+	return 0;
+}
+
+/*! \brief Stream write with constraints checks. */
+#define STREAM_WRITE(stream, maxlen, fn, args...) \
+	if (stream_skip(&(stream), (maxlen), fn(stream, *(maxlen), args)) != KNOT_EOK) { \
+		return KNOT_ESPACE; \
+	}
+
+static int rosedb_log_message(char *stream, size_t *maxlen, knot_pkt_t *pkt,
+                              const char *threat_code, struct query_data *qdata)
+{
+	char dname_buf[KNOT_DNAME_MAXLEN] = {'\0'};
+	struct sockaddr_storage addr;
+	socklen_t addr_len = sizeof(addr);
+	time_t now = time(NULL);
+	struct tm tm;
+	gmtime_r(&now, &tm);
+
+	/* Field 1 Timestamp (UTC). */
+	STREAM_WRITE(stream, maxlen, strftime, "%Y-%m-%d %H:%M:%S\t", &tm);
+
+	/* Field 2/3 Remote, local address. */
+	const struct sockaddr *remote = (const struct sockaddr *)qdata->param->remote;
+	memcpy(&addr, remote, sockaddr_len(remote));
+	int client_port = sockaddr_port(&addr);
+	sockaddr_port_set(&addr, 0);
+	STREAM_WRITE(stream, maxlen, sockaddr_tostr, &addr);
+	STREAM_WRITE(stream, maxlen, snprintf, "\t");
+	getsockname(qdata->param->socket, (struct sockaddr *)&addr, &addr_len);
+	int server_port = sockaddr_port(&addr);
+	sockaddr_port_set(&addr, 0);
+	STREAM_WRITE(stream, maxlen, sockaddr_tostr, &addr);
+	STREAM_WRITE(stream, maxlen, snprintf, "\t");
+
+	/* Field 4/5 Local, remote port. */
+	STREAM_WRITE(stream, maxlen, snprintf, "%d\t%d\t", client_port, server_port);
+
+	/* Field 6 Threat ID. */
+	STREAM_WRITE(stream, maxlen, snprintf, "%s\t", threat_code);
+
+	/* Field 7 - 13 NULL */
+	STREAM_WRITE(stream, maxlen, snprintf, "\t\t\t\t\t\t\t");
+
+	/* Field 14 QNAME */
+	knot_dname_to_str(dname_buf, knot_pkt_qname(qdata->query), sizeof(dname_buf));
+	STREAM_WRITE(stream, maxlen, snprintf, "%s\t", dname_buf);
+
+	/* Field 15 Resolution (0 = local, 1 = lookup)*/
+	STREAM_WRITE(stream, maxlen, snprintf, "0\t");
+
+	/* Field 16 RDATA.
+	 * - Return randomly RDATA in the answer section (probabilistic rotation).
+	 * - Empty if no answer.
+	 */
+	const knot_pktsection_t *ans = knot_pkt_section(pkt, KNOT_ANSWER);
+	if (ans->count > 0) {
+		const knot_rrset_t *rr = &ans->rr[knot_random_uint16_t() % ans->count];
+		int ret = knot_rrset_txt_dump_data(rr, 0, stream, *maxlen, &KNOT_DUMP_STYLE_DEFAULT);
+		if (ret < 0) {
+			return ret;
+		}
+		stream_skip(&stream, maxlen, ret);
+	}
+	STREAM_WRITE(stream, maxlen, snprintf, "\t");
+
+	/* Field 17 Connection type. */
+	STREAM_WRITE(stream, maxlen, snprintf, "%s\t",
+	             net_is_connected(qdata->param->socket) ? "TCP" : "UDP");
+
+	/* Field 18 Query type. */
+	char type_str[16] = { '\0' };
+	knot_rrtype_to_string(knot_pkt_qtype(qdata->query), type_str, sizeof(type_str));
+	STREAM_WRITE(stream, maxlen, snprintf, "%s\t", type_str);
+
+	/* Field 19 First authority. */
+	const knot_pktsection_t *ns = knot_pkt_section(pkt, KNOT_AUTHORITY);
+	if (ns->count > 0 && ns->rr[0].type == KNOT_RRTYPE_NS) {
+		const knot_dname_t *label = knot_ns_name(&ns->rr[0].rrs, 0);
+		memset(dname_buf, 0, sizeof(dname_buf));
+		memcpy(dname_buf, label + 1, *label);
+		STREAM_WRITE(stream, maxlen, snprintf, "%s", dname_buf);
+	}
+
+	return KNOT_EOK;
+}
+
+static int rosedb_send_log(int sock, struct sockaddr *dst_addr, knot_pkt_t *pkt,
+                           const char *threat_code, struct query_data *qdata)
+{
+	char buf[SYSLOG_BUFLEN];
+	char *stream = buf;
+	size_t maxlen = sizeof(buf);
+
+	time_t now = time(NULL);
+	struct tm tm;
+	localtime_r(&now, &tm);
+
+	/* Add facility. */
+	STREAM_WRITE(stream, &maxlen, snprintf, "<%u>", SYSLOG_FACILITY);
+
+	/* Current local time (4.3.2)*/
+	STREAM_WRITE(stream, &maxlen, strftime, "%b %d %H:%M:%S ", &tm);
+
+	/* Host name / Component. */
+	STREAM_WRITE(stream, &maxlen, snprintf, "%s ", conf()->identity);
+	STREAM_WRITE(stream, &maxlen, snprintf, "%s[%lu]: ", PACKAGE_NAME, (unsigned long) getpid());
+
+	/* Prepare log message line. */
+	int ret = rosedb_log_message(stream, &maxlen, pkt, threat_code, qdata);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	/* Send log message line. */
+	sendto(sock, buf, sizeof(buf) - maxlen, 0, dst_addr, sockaddr_len(dst_addr));
+
+	return ret;
+}
+
+static int rosedb_synth_rr(knot_pkt_t *pkt, struct entry *entry, uint16_t qtype)
+{
+	if (qtype != entry->data.type) {
+		return KNOT_EOK; /* Ignore */
+	}
+
+	knot_rrset_t *rr = knot_rrset_new(knot_pkt_qname(pkt), entry->data.type, KNOT_CLASS_IN, &pkt->mm);
+	int ret = knot_rdataset_copy(&rr->rrs, &entry->data.rrs, &pkt->mm);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, rr, KNOT_PF_FREE);
+
+	return ret;
+}
+
+static int rosedb_synth(knot_pkt_t *pkt, const knot_dname_t *key, struct iter *it,
+                        struct query_data *qdata)
+{
+	struct entry entry;
+	int ret = KNOT_EOK;
+	uint16_t qtype = knot_pkt_qtype(qdata->query);
+
+	/* Answer section. */
+	while (ret == KNOT_EOK) {
+		if (cache_iter_val(it, &entry) == 0) {
+			ret = rosedb_synth_rr(pkt, &entry, qtype);
+		}
+		if (cache_iter_next(it) != 0) {
+			break;
+		}
+	}
+
+	/* Authority section. */
+	knot_pkt_begin(pkt, KNOT_AUTHORITY);
+	
+	/* Not found (zone cut if records exist). */
+	ret = cache_iter_begin(it, key);
+	while (ret == KNOT_EOK) {
+		if (cache_iter_val(it, &entry) == 0) {
+			ret = rosedb_synth_rr(pkt, &entry, KNOT_RRTYPE_NS);
+			ret = rosedb_synth_rr(pkt, &entry, KNOT_RRTYPE_SOA);
+		}
+		if (cache_iter_next(it) != 0) {
+			break;
+		}
+	}
+
+	/* Our response is authoritative. */
+	if (knot_wire_get_nscount(pkt->wire) > 0) {
+		knot_wire_set_aa(pkt->wire);
+		if (knot_wire_get_ancount(pkt->wire) == 0) {
+			qdata->rcode = KNOT_RCODE_NXDOMAIN;
+		}
+	}
+
+	/* Send message to syslog. */
+	struct sockaddr_storage syslog_addr;
+	if (sockaddr_set(&syslog_addr, AF_INET, entry.syslog_ip, DEFAULT_PORT) == KNOT_EOK) {
+		int sock = net_unbound_socket(AF_INET, &syslog_addr);
+		if (sock > 0) {
+			rosedb_send_log(sock, (struct sockaddr *)&syslog_addr, pkt,
+			                entry.threat_code, qdata);
+			close(sock);
+		}
+	}
+
+	return ret;
+}
+
+static int rosedb_query_txn(MDB_txn *txn, MDB_dbi dbi, knot_pkt_t *pkt, struct query_data *qdata)
+{
+	struct iter it;
+	int ret = KNOT_EOK;
+
+	/* Find suffix for QNAME. */
+	const knot_dname_t *qname = knot_pkt_qname(qdata->query);
+	const knot_dname_t *key = qname;
+	while (key) {
+		ret = cache_query_fetch(txn, dbi, &it, key);
+		if (ret == 0) { /* Found */
+			break;
+		}
+
+		if (*key == '\0') { /* Last label, not found. */
+			return KNOT_ENOENT;
+		}
+
+		key = knot_wire_next_label(key, qdata->query->wire);
+	}
+
+	/* Synthetize record to response. */
+	ret = rosedb_synth(pkt, key, &it, qdata);
+
+	cache_iter_free(&it);
+	return ret;
+}
+
+static int rosedb_query(int state, knot_pkt_t *pkt, struct query_data *qdata, void *ctx)
+{
+	if (pkt == NULL || qdata == NULL || ctx == NULL) {
+		return KNOT_NS_PROC_FAIL;
+	}
+
+	struct cache *cache = ctx;
+
+	MDB_txn *txn = NULL;
+	int ret = mdb_txn_begin(cache->env, NULL, MDB_RDONLY, &txn);
+	if (ret != 0) { /* Can't start transaction, ignore. */
+		return state;
+	}
+
+	ret = rosedb_query_txn(txn, cache->dbi, pkt, qdata);
+	if (ret != 0) { /* Can't find matching zone, ignore. */
+		mdb_txn_abort(txn);
+		return state;
+	}
+
+	mdb_txn_abort(txn);
+
+	return KNOT_NS_PROC_DONE;
+}
+
+int rosedb_load(struct query_plan *plan, struct query_module *self)
+{
+	if (self == NULL || plan == NULL) {
+		return KNOT_EINVAL;
+	}
+	
+	struct cache *cache = cache_open(self->param, 0, self->mm);
+	if (cache == NULL) {
+		MODULE_ERR("couldn't open db '%s'", self->param);
+		return KNOT_ENOMEM;
+	}
+
+	self->ctx = cache;
+
+	return query_plan_step(plan, QPLAN_BEGIN, rosedb_query, cache);
+}
+
+int rosedb_unload(struct query_module *self)
+{
+	if (self == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	cache_close(self->ctx);
+	return KNOT_EOK;
+}
+
diff --git a/src/knot/modules/rosedb.h b/src/knot/modules/rosedb.h
new file mode 100644
index 0000000000000000000000000000000000000000..9bcccf95f9ed79976821ca1e86ef75e3496de108
--- /dev/null
+++ b/src/knot/modules/rosedb.h
@@ -0,0 +1,41 @@
+/*!
+ * \file rosedb.h
+ *
+ * \author Marek Vavrusa <marek.vavrusa@nic.cz>
+ *
+ * \brief Static resource records
+ *
+ * Accepted configurations:
+ *  * "<path_to_database>"
+ *
+ * The module provides a mean to override responses for certain queries before
+ * the record is searched in the available zones.
+ *
+ * \addtogroup query_processing
+ * @{
+ */
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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/>.
+ */
+
+#pragma once
+
+#include "knot/nameserver/query_module.h"
+
+/*! \brief Module interface. */
+int rosedb_load(struct query_plan *plan, struct query_module *self);
+int rosedb_unload(struct query_module *self);
+
+/*! @} */
diff --git a/src/knot/modules/rosedb_tool.c b/src/knot/modules/rosedb_tool.c
new file mode 100644
index 0000000000000000000000000000000000000000..4d4d9d132304934c3b571610e26f990387d67bb5
--- /dev/null
+++ b/src/knot/modules/rosedb_tool.c
@@ -0,0 +1,325 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include "knot/modules/rosedb.c"
+#include "zscanner/scanner.h"
+#include "libknot/internal/mem.h"
+#include "libknot/internal/getline.h"
+
+static int rosedb_add(struct cache *cache, MDB_txn *txn, int argc, char *argv[]);
+static int rosedb_del(struct cache *cache, MDB_txn *txn, int argc, char *argv[]);
+static int rosedb_get(struct cache *cache, MDB_txn *txn, int argc, char *argv[]);
+static int rosedb_list(struct cache *cache, MDB_txn *txn, int argc, char *argv[]);
+static int rosedb_import(struct cache *cache, MDB_txn *txn, int argc, char *argv[]);
+
+struct tool_action {
+	const char *name;
+	int (*func)(struct cache *, MDB_txn *, int, char *[]);
+	int min_args;
+	const char *info;
+};
+
+#define TOOL_ACTION_MAXARG 7
+#define TOOL_ACTION_COUNT 5
+static struct tool_action TOOL_ACTION[TOOL_ACTION_COUNT] = {
+{ "add",    rosedb_add,    6, "<zone> <rrtype> <ttl> <rdata> <threat_code> <syslog_ip>" },
+{ "del",    rosedb_del,    1, "<zone> [rrtype]" },
+{ "get",    rosedb_get,    1, "<zone> [rrtype]" },
+{ "import", rosedb_import, 1, "<file>" },
+{ "list",   rosedb_list,   0, "" }
+};
+
+static int help(void)
+{
+	printf("Usage: rosedb_tool <dbdir> <action> [params]\n");
+	printf("Actions:\n");
+	for (unsigned i = 0; i < TOOL_ACTION_COUNT; ++i) {
+		struct tool_action *ta = &TOOL_ACTION[i];
+		printf("\t%s %s\n", ta->name, ta->info);
+	}
+	return EXIT_FAILURE;
+}
+
+/* Global instance of RR scanner. */
+static void parse_err(zs_scanner_t *s) {
+	fprintf(stderr, "failed to parse RDATA: %s\n", zs_strerror(s->error_code));
+}
+static zs_scanner_t *g_scanner = NULL;
+
+int main(int argc, char *argv[])
+{
+	if (argc < 3) {
+		return help();
+	}
+
+	/* Get mandatory parameters. */
+	int ret = EXIT_SUCCESS;
+	char *dbdir  = argv[1];
+	char *action = argv[2];
+	argv += 3;
+	argc -= 3;
+
+	g_scanner = zs_scanner_create(".", KNOT_CLASS_IN, 0, NULL, parse_err, NULL);
+	if (g_scanner == NULL) {
+		return EXIT_FAILURE;
+	}
+
+	/* Open cache for operations. */
+	struct cache *cache = cache_open(dbdir, 0, NULL);
+	if (cache == NULL) {
+		fprintf(stderr, "failed to open db '%s'\n", dbdir);
+		zs_scanner_free(g_scanner);
+		return EXIT_FAILURE;
+	}
+
+	/* Execute action. */
+	bool found = false;
+	for (unsigned i = 0; i < TOOL_ACTION_COUNT; ++i) {
+		struct tool_action *ta = &TOOL_ACTION[i];
+		if (strcmp(ta->name, action) == 0) {
+
+			/* Check param count. */
+			if (argc < ta->min_args) {
+				break;
+			}
+
+			/* Now set as found. */
+			found = true;
+
+			MDB_txn *txn = NULL;
+			int ret = mdb_txn_begin(cache->env, NULL, 0, &txn);
+			if (ret != MDB_SUCCESS) {
+				fprintf(stderr, "failed to open transaction, aborting\n");
+				break;
+			}
+
+			/* Execute operation handler. */
+			ret = ta->func(cache, txn, argc, argv);
+			if (ret != 0) {
+				fprintf(stderr, "'%s' failed, aborting transaction\n", action);
+				mdb_txn_abort(txn);
+			} else {
+				mdb_txn_commit(txn);
+			}
+
+			break;
+		}
+	}
+	
+	cache_close(cache);
+	zs_scanner_free(g_scanner);
+
+	if (!found) {
+		return help();
+	}
+	
+	return ret;
+}
+
+static int parse_rdata(struct entry *entry, const char *owner, const char *rrtype, const char *rdata,
+		       int ttl, mm_ctx_t *mm)
+{
+	knot_rdataset_init(&entry->data.rrs);
+	int ret = knot_rrtype_from_string(rrtype, &entry->data.type);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	/* Synthetize RR line */
+	char *rr_line = sprintf_alloc("%s %u IN %s %s\n", owner, ttl, rrtype, rdata);
+	ret = zs_scanner_parse(g_scanner, rr_line, rr_line + strlen(rr_line), true);
+	free(rr_line);
+
+	/* Write parsed RDATA. */
+	if (ret == KNOT_EOK) {
+		knot_rdata_t rr[knot_rdata_array_size(g_scanner->r_data_length)];
+		knot_rdata_init(rr, g_scanner->r_data_length, g_scanner->r_data, ttl);
+		ret = knot_rdataset_add(&entry->data.rrs, rr, mm);
+	}
+
+	return ret;
+}
+
+static int rosedb_add(struct cache *cache, MDB_txn *txn, int argc, char *argv[])
+{
+	printf("ADD %s\t%s\t%s\t%s\t%s\t%s\n", argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
+
+	knot_dname_t key[KNOT_DNAME_MAXLEN] = { '\0' };
+	knot_dname_from_str(key, argv[0], sizeof(key));
+	knot_dname_to_lower(key);
+
+	struct entry entry;
+	int ret = parse_rdata(&entry, argv[0], argv[1], argv[3], atoi(argv[2]), cache->pool);
+	entry.threat_code = argv[4];
+	entry.syslog_ip   = argv[5];
+	if (ret != 0) {
+		fprintf(stderr, "PARSE: %s\n", knot_strerror(ret));
+		return ret;
+	}
+
+	ret = cache_insert(txn, cache->dbi, key, &entry);
+	if (ret != 0) {
+		fprintf(stderr, "%s\n", mdb_strerror(ret));
+	}
+
+	return ret;
+}
+
+static int rosedb_del(struct cache *cache, MDB_txn *txn, int argc, char *argv[])
+{
+	printf("DEL %s\n", argv[0]);
+
+	knot_dname_t key[KNOT_DNAME_MAXLEN] = { '\0' };
+	knot_dname_from_str(key, argv[0], sizeof(key));
+	knot_dname_to_lower(key);
+
+	int ret = cache_remove(txn, cache->dbi, key);
+	if (ret != 0) {
+		fprintf(stderr, "%s\n", mdb_strerror(ret));
+	}
+
+	return ret;
+}
+
+static int rosedb_get(struct cache *cache, MDB_txn *txn, int argc, char *argv[])
+{
+	knot_dname_t key[KNOT_DNAME_MAXLEN] = { '\0' };
+	knot_dname_from_str(key, argv[0], sizeof(key));
+	knot_dname_to_lower(key);
+
+	char type_str[16] = { '\0' };
+
+	struct iter it;
+	int ret = cache_query_fetch(txn, cache->dbi, &it, key);
+	while (ret == 0) {
+		struct entry entry;
+		cache_iter_val(&it, &entry);
+		knot_rdata_t *rd = knot_rdataset_at(&entry.data.rrs, 0);
+		knot_rrtype_to_string(entry.data.type, type_str, sizeof(type_str));
+		printf("%s\t%s\tTTL=%u\tRDLEN=%u\t%s\t%s\n", argv[0], type_str,
+		       knot_rdata_ttl(rd), knot_rdata_rdlen(rd), entry.threat_code, entry.syslog_ip);
+		if (cache_iter_next(&it) != 0) {
+			break;
+		}
+	}
+
+	cache_iter_free(&it);
+
+	return ret;
+}
+
+static int rosedb_list(struct cache *cache, MDB_txn *txn, int argc, char *argv[])
+{
+	MDB_cursor *cursor = cursor_acquire(txn, cache->dbi);
+	MDB_val key, data;
+	char dname_str[KNOT_DNAME_MAXLEN] = {'\0'};
+	char type_str[16] = { '\0' };
+
+	int ret = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
+	while (ret == 0) {
+		struct entry entry;
+		unpack_entry(&data, &entry);
+		knot_dname_to_str(dname_str, key.mv_data, sizeof(dname_str));
+		knot_rrtype_to_string(entry.data.type, type_str, sizeof(type_str));
+		printf("%s\t%s RDATA=%zuB\t%s\t%s\n", dname_str, type_str,
+		       knot_rdataset_size(&entry.data.rrs), entry.threat_code, entry.syslog_ip);
+
+		ret = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
+	}
+
+	cursor_release(cursor);
+
+	return KNOT_EOK;
+}
+
+static char *trim(char *line)
+{
+	int last = strlen(line) - 1;
+	if (line[last] == '\n') {
+		line[last] = '\0';
+		last -= 1;
+	}
+	if (*line == '"') {
+		line[last] = '\0';
+		line += 1;
+	}
+
+	return line;
+}
+
+static int rosedb_import_line(struct cache *cache, MDB_txn *txn, char *line, const char *file, int lineno)
+{
+	int ret = 0;
+	int argc = 0;
+	char *argv[TOOL_ACTION_MAXARG];
+
+	/* Tokenize */
+	char *saveptr = line;
+	char *token = NULL;
+	while ((token = strtok_r(saveptr, ";\t", &saveptr)) != NULL) {
+		token = trim(token);
+		if (*token == '\0') {
+			continue;
+		}
+		if (argc <= TOOL_ACTION_MAXARG) {
+			argv[argc] = token;
+			argc += 1;
+		} else {
+			fprintf(stderr, "%s#%d command '%s' - too much parameters (%d)\n",
+			        file, lineno, line, argc);
+			return KNOT_EPARSEFAIL;
+		}
+	}
+
+	if (argc < 1) {
+		fprintf(stderr, "%s#%d command '%s' - command not recognized\n", file, lineno, line);
+		return KNOT_EOK; /* Ignore NOOP */
+	}
+
+	/* Execute action. */
+	bool found = false;
+	for (unsigned i = 0; i < TOOL_ACTION_COUNT; ++i) {
+		struct tool_action *ta = &TOOL_ACTION[i];
+		if (strcmp(ta->name, argv[0]) == 0) {
+			if (argc < ta->min_args) {
+				return help();
+			}
+			found = true;
+			ret = ta->func(cache, txn, argc - 1, argv + 1);
+			break;
+		}
+	}
+	if (!found) {
+		fprintf(stderr, "%s#%d command '%s' - command not recognized\n", file, lineno, line);
+		return KNOT_EPARSEFAIL;
+	}
+
+	return ret;
+}
+
+static int rosedb_import(struct cache *cache, MDB_txn *txn, int argc, char *argv[])
+{
+	printf("IMPORT %s\n", argv[0]);
+
+	int ret = 0;
+	char *line = NULL;
+	int lineno = 0;
+	size_t line_len = 0;
+	FILE *fp = fopen(argv[0], "r");
+	if (fp == NULL) {
+		return KNOT_ENOENT;
+	}
+
+	while (knot_getline(&line, &line_len, fp) != -1) {
+		lineno += 1;
+		ret = rosedb_import_line(cache, txn, line, argv[0], lineno);
+		if (ret != 0) {
+			break;
+		}
+	}
+
+	free(line);
+	fclose(fp);
+
+	return ret;
+}
diff --git a/src/knot/nameserver/process_query.c b/src/knot/nameserver/process_query.c
index 5df1d9ec259bfd7f6090a5d373a32d390a3fcdd8..c795f2d8e7cdc48da1732959eaaf19c939e3142e 100644
--- a/src/knot/nameserver/process_query.c
+++ b/src/knot/nameserver/process_query.c
@@ -435,7 +435,7 @@ static int process_query_out(knot_layer_t *ctx, knot_pkt_t *pkt)
 
 	/* Check parse state. */
 	knot_pkt_t *query = qdata->query;
-	int next_state = KNOT_NS_PROC_DONE;
+	int next_state = KNOT_NS_PROC_FULL;
 	if (query->parsed < query->size) {
 		dbg_ns("%s: incompletely parsed query, FORMERR\n", __func__);
 		knot_pkt_clear(pkt);
@@ -462,18 +462,20 @@ static int process_query_out(knot_layer_t *ctx, knot_pkt_t *pkt)
 	}
 
 	/* Answer based on qclass. */
-	switch (knot_pkt_qclass(pkt)) {
-	case KNOT_CLASS_CH:
-		next_state = query_chaos(pkt, ctx);
-		break;
-	case KNOT_CLASS_ANY:
-	case KNOT_CLASS_IN:
-		next_state = query_internet(pkt, ctx);
-		break;
-	default:
-		qdata->rcode = KNOT_RCODE_REFUSED;
-		next_state = KNOT_NS_PROC_FAIL;
-		break;
+	if (next_state != KNOT_NS_PROC_DONE) {
+		switch (knot_pkt_qclass(pkt)) {
+		case KNOT_CLASS_CH:
+			next_state = query_chaos(pkt, ctx);
+			break;
+		case KNOT_CLASS_ANY:
+		case KNOT_CLASS_IN:
+			next_state = query_internet(pkt, ctx);
+			break;
+		default:
+			qdata->rcode = KNOT_RCODE_REFUSED;
+			next_state = KNOT_NS_PROC_FAIL;
+			break;
+		}
 	}
 
 	/*
diff --git a/src/knot/nameserver/process_query.h b/src/knot/nameserver/process_query.h
index f0fbc92415b84bebeb9a73ae0d84a03d611bc2ab..ba5aaaa34095946e924c06dc7eca427145015167 100644
--- a/src/knot/nameserver/process_query.h
+++ b/src/knot/nameserver/process_query.h
@@ -38,7 +38,7 @@ const knot_layer_api_t *process_query_get_module(void);
 /*! \brief Query processing logging common base. */
 #define NS_PROC_LOG(severity, remote, zone_name, operation, msg, ...) do { \
 	char addr_str[SOCKADDR_STRLEN] = {0}; \
-	sockaddr_tostr(remote, addr_str, sizeof(addr_str)); \
+	sockaddr_tostr(addr_str, sizeof(addr_str), remote); \
 	log_msg_zone(severity, zone_name, operation ", %s: " msg, \
 	             addr_str, ##__VA_ARGS__); \
 	} while (0)
diff --git a/src/knot/nameserver/query_module.c b/src/knot/nameserver/query_module.c
index 6fb44a14e6345f67fd0a9917f89c70fe3bba8470..d7c2475cec50511db495cdc2a1bd53b63da07664 100644
--- a/src/knot/nameserver/query_module.c
+++ b/src/knot/nameserver/query_module.c
@@ -5,6 +5,10 @@
 
 /* Compiled-in module headers. */
 #include "knot/modules/synth_record.h"
+#include "knot/modules/dnsproxy.h"
+#ifdef HAVE_ROSEDB 
+#include "knot/modules/rosedb.h"
+#endif
 #if USE_DNSTAP
 #include "knot/modules/dnstap.h"
 #endif
@@ -19,6 +23,10 @@ struct compiled_module {
 /*! \note All modules should be dynamically loaded later on. */
 struct compiled_module MODULES[] = {
         { "synth_record", &synth_record_load, &synth_record_unload },
+        { "dnsproxy", &dnsproxy_load, &dnsproxy_unload },
+#ifdef HAVE_ROSEDB
+        { "rosedb", &rosedb_load, &rosedb_unload },
+#endif
 #if USE_DNSTAP
         { "dnstap",       &dnstap_load,       &dnstap_unload }
 #endif
diff --git a/src/knot/server/rrl.c b/src/knot/server/rrl.c
index 6ec7b350171bca3f07bb49e0b255851566155948..22a01c0ab24c04a36746e3b887676d3334376e06 100644
--- a/src/knot/server/rrl.c
+++ b/src/knot/server/rrl.c
@@ -277,7 +277,7 @@ static void rrl_log_state(const struct sockaddr_storage *ss, uint16_t flags, uin
 {
 #ifdef RRL_ENABLE_LOG
 	char addr_str[SOCKADDR_STRLEN] = {0};
-	sockaddr_tostr(ss, addr_str, sizeof(addr_str));
+	sockaddr_tostr(addr_str, sizeof(addr_str), ss);
 
 	const char *what = "leaves";
 	if (flags & RRL_BF_ELIMIT) {
diff --git a/src/knot/server/server.c b/src/knot/server/server.c
index 191bec09709e58572da4f7fe3b7551d481f5260f..9a8cdc9516be10a1e8411e440f86a2227e90fd6a 100644
--- a/src/knot/server/server.c
+++ b/src/knot/server/server.c
@@ -110,7 +110,7 @@ static int server_init_iface(iface_t *new_if, conf_iface_t *cfg_if)
 
 	/* Convert to string address format. */
 	char addr_str[SOCKADDR_STRLEN] = {0};
-	sockaddr_tostr(&cfg_if->addr, addr_str, sizeof(addr_str));
+	sockaddr_tostr(addr_str, sizeof(addr_str), &cfg_if->addr);
 
 	/* Create bound UDP socket. */
 	int sock = net_bound_socket(SOCK_DGRAM, &cfg_if->addr);
@@ -162,7 +162,7 @@ static void remove_ifacelist(struct ref *p)
 	char addr_str[SOCKADDR_STRLEN] = {0};
 	iface_t *n = NULL, *m = NULL;
 	WALK_LIST_DELSAFE(n, m, ifaces->u) {
-		sockaddr_tostr(&n->addr, addr_str, sizeof(addr_str));
+		sockaddr_tostr(addr_str, sizeof(addr_str), &n->addr);
 		log_info("removing interface '%s'", addr_str);
 		server_remove_iface(n);
 	}
@@ -219,7 +219,7 @@ static int reconfigure_sockets(const struct conf *conf, server_t *s)
 		if (found_match) {
 			rem_node((node_t *)m);
 		} else {
-			sockaddr_tostr(&cfg_if->addr, addr_str, sizeof(addr_str));
+			sockaddr_tostr(addr_str, sizeof(addr_str), &cfg_if->addr);
 			log_info("binding to interface '%s'", addr_str);
 
 			/* Create new interface. */
diff --git a/src/knot/server/tcp-handler.c b/src/knot/server/tcp-handler.c
index a10a3a554ddbea0d37d37fcec76c71ba6b3ff09d..abe0be547f0e3449faeb7917b6d80c08d7477363 100644
--- a/src/knot/server/tcp-handler.c
+++ b/src/knot/server/tcp-handler.c
@@ -84,7 +84,7 @@ static enum fdset_sweep_state tcp_sweep(fdset_t *set, int i, void *data)
 
 	/* Translate */
 	char addr_str[SOCKADDR_STRLEN] = {0};
-	sockaddr_tostr(&ss, addr_str, sizeof(addr_str));
+	sockaddr_tostr(addr_str, sizeof(addr_str), &ss);
 
 	log_notice("connection terminated due to inactivity, address '%s'", addr_str);
 	close(fd);
@@ -124,7 +124,7 @@ static int tcp_handle(tcp_context_t *tcp, int fd,
 		if (ret == KNOT_EAGAIN) {
 			rcu_read_lock();
 			char addr_str[SOCKADDR_STRLEN] = {0};
-			sockaddr_tostr(&ss, addr_str, sizeof(addr_str));
+			sockaddr_tostr(addr_str, sizeof(addr_str), &ss);
 			log_warning("connection timed out, address '%s', "
 			            "timeout %d seconds",
 			            addr_str, conf()->max_conn_idle);
diff --git a/src/knot/zone/zone-dump.c b/src/knot/zone/zone-dump.c
index 057004646aab690de75ab8399c9021396a073ac0..406f1c345a6631687801b95d09117d45d4ddd8e2 100644
--- a/src/knot/zone/zone-dump.c
+++ b/src/knot/zone/zone-dump.c
@@ -226,7 +226,7 @@ int zone_dump_text(zone_contents_t *zone, const struct sockaddr_storage *from, F
 	// If a master server is configured, dump info about it.
 	if (from) {
 		char addr_str[SOCKADDR_STRLEN] = {0};
-		sockaddr_tostr(from, addr_str, sizeof(addr_str));
+		sockaddr_tostr(addr_str, sizeof(addr_str), from);
 
 		fprintf(file, ";; Transfered from %s\n", addr_str);
 	}
diff --git a/src/libknot/internal/base32hex.c b/src/libknot/internal/base32hex.c
index dcdaad05bf196b0375eb05820bd54e727ebe86a5..9ae0ad6eb5c98b9cc01f695acd4c4b68aecb335e 100644
--- a/src/libknot/internal/base32hex.c
+++ b/src/libknot/internal/base32hex.c
@@ -17,8 +17,8 @@
 #include "libknot/internal/base32hex.h"
 #include "libknot/errcode.h"
 
-#include <stdlib.h>			// malloc
-#include <stdint.h>			// uint8_t
+#include <stdlib.h>
+#include <stdint.h>
 
 /*! \brief Maximal length of binary input to Base32hex encoding. */
 #define MAX_BIN_DATA_LEN	((INT32_MAX / 8) * 5)
@@ -85,12 +85,6 @@ int32_t base32hex_encode(const uint8_t  *in,
                          uint8_t        *out,
                          const uint32_t out_len)
 {
-	uint8_t		rest_len = in_len % 5;
-	const uint8_t	*data = in;
-	const uint8_t	*stop = in + in_len - rest_len;
-	uint8_t		*text = out;
-	uint8_t		num;
-
 	// Checking inputs.
 	if (in == NULL || out == NULL) {
 		return KNOT_EINVAL;
@@ -99,158 +93,69 @@ int32_t base32hex_encode(const uint8_t  *in,
 		return KNOT_ERANGE;
 	}
 
+	uint8_t		rest_len = in_len % 5;
+	const uint8_t	*stop = in + in_len - rest_len;
+	uint8_t		*text = out;
+
 	// Encoding loop takes 5 bytes and creates 8 characters.
-	while (data < stop) {
-		// Computing 1. Base32hex character.
-		num = *data >> 3;
-		*text++ = base32hex_enc[num];
-
-		// Computing 2. Base32hex character.
-		num = (*data++ & 0x07) << 2;
-		num += *data >> 6;
-		*text++ = base32hex_enc[num];
-
-		// Computing 3. Base32hex character.
-		num = (*data & 0x3E) >> 1;
-		*text++ = base32hex_enc[num];
-
-		// Computing 4. Base32hex character.
-		num = (*data++ & 0x01) << 4;
-		num += *data >> 4;
-		*text++ = base32hex_enc[num];
-
-		// Computing 5. Base32hex character.
-		num = (*data++ & 0x0F) << 1;
-		num += *data >> 7;
-		*text++ = base32hex_enc[num];
-
-		// Computing 6. Base32hex character.
-		num = (*data & 0x7C) >> 2;
-		*text++ = base32hex_enc[num];
-
-		// Computing 7. Base32hex character.
-		num = (*data++ & 0x03) << 3;
-		num += *data >> 5;
-		*text++ = base32hex_enc[num];
-
-		// Computing 8. Base32hex character.
-		num = *data++ & 0x1F;
-		*text++ = base32hex_enc[num];
+	while (in < stop) {
+		text[0] = base32hex_enc[in[0] >> 3];
+		text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6];
+		text[2] = base32hex_enc[(in[1] & 0x3E) >> 1];
+		text[3] = base32hex_enc[(in[1] & 0x01) << 4 | in[2] >> 4];
+		text[4] = base32hex_enc[(in[2] & 0x0F) << 1 | in[3] >> 7];
+		text[5] = base32hex_enc[(in[3] & 0x7C) >> 2];
+		text[6] = base32hex_enc[(in[3] & 0x03) << 3 | in[4] >> 5];
+		text[7] = base32hex_enc[in[4] & 0x1F];
+		text += 8;
+		in += 5;
 	}
 
 	// Processing of padding, if any.
 	switch (rest_len) {
-	// Input data has 4-byte last block => 1-char padding.
 	case 4:
-		// Computing 1. Base32hex character.
-		num = *data >> 3;
-		*text++ = base32hex_enc[num];
-
-		// Computing 2. Base32hex character.
-		num = (*data++ & 0x07) << 2;
-		num += *data >> 6;
-		*text++ = base32hex_enc[num];
-
-		// Computing 3. Base32hex character.
-		num = (*data & 0x3E) >> 1;
-		*text++ = base32hex_enc[num];
-
-		// Computing 4. Base32hex character.
-		num = (*data++ & 0x01) << 4;
-		num += *data >> 4;
-		*text++ = base32hex_enc[num];
-
-		// Computing 5. Base32hex character.
-		num = (*data++ & 0x0F) << 1;
-		num += *data >> 7;
-		*text++ = base32hex_enc[num];
-
-		// Computing 6. Base32hex character.
-		num = (*data & 0x7C) >> 2;
-		*text++ = base32hex_enc[num];
-
-		// Computing 7. Base32hex character.
-		num = (*data++ & 0x03) << 3;
-		*text++ = base32hex_enc[num];
-
-		// 1 padding character.
-		*text++ = base32hex_pad;
-
+		text[0] = base32hex_enc[in[0] >> 3];
+		text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6];
+		text[2] = base32hex_enc[(in[1] & 0x3E) >> 1];
+		text[3] = base32hex_enc[(in[1] & 0x01) << 4 | in[2] >> 4];
+		text[4] = base32hex_enc[(in[2] & 0x0F) << 1 | in[3] >> 7];
+		text[5] = base32hex_enc[(in[3] & 0x7C) >> 2];
+		text[6] = base32hex_enc[(in[3] & 0x03) << 3];
+		text[7] = base32hex_pad;
+		text += 8;
 		break;
-	// Input data has 3-byte last block => 3-char padding.
 	case 3:
-		// Computing 1. Base32hex character.
-		num = *data >> 3;
-		*text++ = base32hex_enc[num];
-
-		// Computing 2. Base32hex character.
-		num = (*data++ & 0x07) << 2;
-		num += *data >> 6;
-		*text++ = base32hex_enc[num];
-
-		// Computing 3. Base32hex character.
-		num = (*data & 0x3E) >> 1;
-		*text++ = base32hex_enc[num];
-
-		// Computing 4. Base32hex character.
-		num = (*data++ & 0x01) << 4;
-		num += *data >> 4;
-		*text++ = base32hex_enc[num];
-
-		// Computing 5. Base32hex character.
-		num = (*data++ & 0x0F) << 1;
-		*text++ = base32hex_enc[num];
-
-		// 3 padding characters.
-		*text++ = base32hex_pad;
-		*text++ = base32hex_pad;
-		*text++ = base32hex_pad;
-
+		text[0] = base32hex_enc[in[0] >> 3];
+		text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6];
+		text[2] = base32hex_enc[(in[1] & 0x3E) >> 1];
+		text[3] = base32hex_enc[(in[1] & 0x01) << 4 | in[2] >> 4];
+		text[4] = base32hex_enc[(in[2] & 0x0F) << 1];
+		text[5] = base32hex_pad;
+		text[6] = base32hex_pad;
+		text[7] = base32hex_pad;
+		text += 8;
 		break;
-	// Input data has 2-byte last block => 4-char padding.
 	case 2:
-		// Computing 1. Base32hex character.
-		num = *data >> 3;
-		*text++ = base32hex_enc[num];
-
-		// Computing 2. Base32hex character.
-		num = (*data++ & 0x07) << 2;
-		num += *data >> 6;
-		*text++ = base32hex_enc[num];
-
-		// Computing 3. Base32hex character.
-		num = (*data & 0x3E) >> 1;
-		*text++ = base32hex_enc[num];
-
-		// Computing 4. Base32hex character.
-		num = (*data++ & 0x01) << 4;
-		*text++ = base32hex_enc[num];
-
-		// 4 padding characters.
-		*text++ = base32hex_pad;
-		*text++ = base32hex_pad;
-		*text++ = base32hex_pad;
-		*text++ = base32hex_pad;
-
+		text[0] = base32hex_enc[in[0] >> 3];
+		text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6];
+		text[2] = base32hex_enc[(in[1] & 0x3E) >> 1];
+		text[3] = base32hex_enc[(in[1] & 0x01) << 4];
+		text[4] = base32hex_pad;
+		text[5] = base32hex_pad;
+		text[6] = base32hex_pad;
+		text[7] = base32hex_pad;
+		text += 8;
 		break;
-	// Input data has 1-byte last block => 6-char padding.
 	case 1:
-		// Computing 1. Base32hex character.
-		num = *data >> 3;
-		*text++ = base32hex_enc[num];
-
-		// Computing 2. Base32hex character.
-		num = (*data++ & 0x07) << 2;
-		*text++ = base32hex_enc[num];
-
-		// 6 padding characters.
-		*text++ = base32hex_pad;
-		*text++ = base32hex_pad;
-		*text++ = base32hex_pad;
-		*text++ = base32hex_pad;
-		*text++ = base32hex_pad;
-		*text++ = base32hex_pad;
-
+		text[0] = base32hex_enc[in[0] >> 3];
+		text[1] = base32hex_enc[(in[0] & 0x07) << 2];
+		text[2] = base32hex_pad;
+		text[3] = base32hex_pad;
+		text[4] = base32hex_pad;
+		text[5] = base32hex_pad;
+		text[6] = base32hex_pad;
+		text[7] = base32hex_pad;
+		text += 8;
 		break;
 	}
 
@@ -292,12 +197,6 @@ int32_t base32hex_decode(const uint8_t  *in,
                          uint8_t        *out,
                          const uint32_t out_len)
 {
-	const uint8_t	*data = in;
-	const uint8_t	*stop = in + in_len;
-	uint8_t		*bin = out;
-	uint8_t		pad_len = 0;
-	uint8_t		c1, c2, c3, c4, c5, c6, c7, c8;
-
 	// Checking inputs.
 	if (in == NULL || out == NULL) {
 		return KNOT_EINVAL;
@@ -309,17 +208,22 @@ int32_t base32hex_decode(const uint8_t  *in,
 		return KNOT_BASE32HEX_ESIZE;
 	}
 
+	const uint8_t	*stop = in + in_len;
+	uint8_t		*bin = out;
+	uint8_t		pad_len = 0;
+	uint8_t		c1, c2, c3, c4, c5, c6, c7, c8;
+
 	// Decoding loop takes 8 characters and creates 5 bytes.
-	while (data < stop) {
+	while (in < stop) {
 		// Filling and transforming 8 Base32hex chars.
-		c1 = base32hex_dec[*data++];
-		c2 = base32hex_dec[*data++];
-		c3 = base32hex_dec[*data++];
-		c4 = base32hex_dec[*data++];
-		c5 = base32hex_dec[*data++];
-		c6 = base32hex_dec[*data++];
-		c7 = base32hex_dec[*data++];
-		c8 = base32hex_dec[*data++];
+		c1 = base32hex_dec[in[0]];
+		c2 = base32hex_dec[in[1]];
+		c3 = base32hex_dec[in[2]];
+		c4 = base32hex_dec[in[3]];
+		c5 = base32hex_dec[in[4]];
+		c6 = base32hex_dec[in[5]];
+		c7 = base32hex_dec[in[6]];
+		c8 = base32hex_dec[in[7]];
 
 		// Check 8. char if is bad or padding.
 		if (c8 >= PD) {
@@ -378,37 +282,38 @@ int32_t base32hex_decode(const uint8_t  *in,
 
 		// Computing of output data based on padding length.
 		switch (pad_len) {
-		// No padding => output has 5 bytess.
 		case 0:
-			*bin++ = (c1 << 3) + (c2 >> 2);
-			*bin++ = (c2 << 6) + (c3 << 1) + (c4 >> 4);
-			*bin++ = (c4 << 4) + (c5 >> 1);
-			*bin++ = (c5 << 7) + (c6 << 2) + (c7 >> 3);
-			*bin++ = (c7 << 5) + c8;
+			bin[4] = (c7 << 5) + c8;
+		case 1:
+			bin[3] = (c5 << 7) + (c6 << 2) + (c7 >> 3);
+		case 3:
+			bin[2] = (c4 << 4) + (c5 >> 1);
+		case 4:
+			bin[1] = (c2 << 6) + (c3 << 1) + (c4 >> 4);
+		case 6:
+			bin[0] = (c1 << 3) + (c2 >> 2);
+		}
+
+		// Update output end.
+		switch (pad_len) {
+		case 0:
+			bin += 5;
 			break;
-		// 1-char padding => output has 4 bytes.
 		case 1:
-			*bin++ = (c1 << 3) + (c2 >> 2);
-			*bin++ = (c2 << 6) + (c3 << 1) + (c4 >> 4);
-			*bin++ = (c4 << 4) + (c5 >> 1);
-			*bin++ = (c5 << 7) + (c6 << 2) + (c7 >> 3);
+			bin += 4;
 			break;
-		// 3-char padding => output has 3 bytes.
 		case 3:
-			*bin++ = (c1 << 3) + (c2 >> 2);
-			*bin++ = (c2 << 6) + (c3 << 1) + (c4 >> 4);
-			*bin++ = (c4 << 4) + (c5 >> 1);
+			bin += 3;
 			break;
-		// 4-char padding => output has 2 bytes.
 		case 4:
-			*bin++ = (c1 << 3) + (c2 >> 2);
-			*bin++ = (c2 << 6) + (c3 << 1) + (c4 >> 4);
+			bin += 2;
 			break;
-		// 6-char padding => output has 1 byte.
 		case 6:
-			*bin++ = (c1 << 3) + (c2 >> 2);
+			bin += 1;
 			break;
 		}
+
+		in += 8;
 	}
 
 	return (bin - out);
diff --git a/src/libknot/internal/base64.c b/src/libknot/internal/base64.c
index f7f6f1d3f1d83ea514346b3b602101c297bbd8b8..9dc554339e0434d969567134c1933fa15cdfe859 100644
--- a/src/libknot/internal/base64.c
+++ b/src/libknot/internal/base64.c
@@ -17,8 +17,8 @@
 #include "libknot/internal/base64.h"
 #include "libknot/errcode.h"
 
-#include <stdlib.h>			// malloc
-#include <stdint.h>			// uint8_t
+#include <stdlib.h>
+#include <stdint.h>
 
 /*! \brief Maximal length of binary input to Base64 encoding. */
 #define MAX_BIN_DATA_LEN	((INT32_MAX / 4) * 3)
@@ -86,12 +86,6 @@ int32_t base64_encode(const uint8_t  *in,
                       uint8_t        *out,
                       const uint32_t out_len)
 {
-	uint8_t		rest_len = in_len % 3;
-	const uint8_t	*data = in;
-	const uint8_t	*stop = in + in_len - rest_len;
-	uint8_t		*text = out;
-	uint8_t		num;
-
 	// Checking inputs.
 	if (in == NULL || out == NULL) {
 		return KNOT_EINVAL;
@@ -100,62 +94,35 @@ int32_t base64_encode(const uint8_t  *in,
 		return KNOT_ERANGE;
 	}
 
+	uint8_t		rest_len = in_len % 3;
+	const uint8_t	*stop = in + in_len - rest_len;
+	uint8_t		*text = out;
+
 	// Encoding loop takes 3 bytes and creates 4 characters.
-	while (data < stop) {
-		// Computing 1. Base64 character.
-		num = *data >> 2;
-		*text++ = base64_enc[num];
-
-		// Computing 2. Base64 character.
-		num = (*data++ & 0x03) << 4;
-		num += *data >> 4;
-		*text++ = base64_enc[num];
-
-		// Computing 3. Base64 character.
-		num = (*data++ & 0x0F) << 2;
-		num += *data >> 6;
-		*text++ = base64_enc[num];
-
-		// Computing 4. Base64 character.
-		num = *data++ & 0x3F;
-		*text++ = base64_enc[num];
+	while (in < stop) {
+		text[0] = base64_enc[in[0] >> 2];
+		text[1] = base64_enc[(in[0] & 0x03) << 4 | in[1] >> 4];
+		text[2] = base64_enc[(in[1] & 0x0F) << 2 | in[2] >> 6];
+		text[3] = base64_enc[in[2] & 0x3F];
+		text += 4;
+		in += 3;
 	}
 
 	// Processing of padding, if any.
 	switch (rest_len) {
-	// Input data has 2-byte last block => 1-char padding.
 	case 2:
-		// Computing 1. Base64 character.
-		num = *data >> 2;
-		*text++ = base64_enc[num];
-
-		// Computing 2. Base64 character.
-		num = (*data++ & 0x03) << 4;
-		num += *data >> 4;
-		*text++ = base64_enc[num];
-
-		// Computing 3. Base64 character.
-		num = (*data++ & 0x0F) << 2;
-		*text++ = base64_enc[num];
-
-		// 1 padding character.
-		*text++ = base64_pad;
-
+		text[0] = base64_enc[in[0] >> 2];
+		text[1] = base64_enc[(in[0] & 0x03) << 4 | in[1] >> 4];
+		text[2] = base64_enc[(in[1] & 0x0F) << 2];
+		text[3] = base64_pad;
+		text += 4;
 		break;
-	// Input data has 1-byte last block => 2-char padding.
 	case 1:
-		// Computing 1. Base64 character.
-		num = *data >> 2;
-		*text++ = base64_enc[num];
-
-		// Computing 2. Base64 character.
-		num = (*data++ & 0x03) << 4;
-		*text++ = base64_enc[num];
-
-		// 2 padding character.
-		*text++ = base64_pad;
-		*text++ = base64_pad;
-
+		text[0] = base64_enc[in[0] >> 2];
+		text[1] = base64_enc[(in[0] & 0x03) << 4];
+		text[2] = base64_pad;
+		text[3] = base64_pad;
+		text += 4;
 		break;
 	}
 
@@ -197,12 +164,6 @@ int32_t base64_decode(const uint8_t  *in,
                       uint8_t        *out,
                       const uint32_t out_len)
 {
-	const uint8_t	*data = in;
-	const uint8_t	*stop = in + in_len;
-	uint8_t		*bin = out;
-	uint8_t		pad_len = 0;
-	uint8_t		c1, c2, c3, c4;
-
 	// Checking inputs.
 	if (in == NULL || out == NULL) {
 		return KNOT_EINVAL;
@@ -214,13 +175,18 @@ int32_t base64_decode(const uint8_t  *in,
 		return KNOT_BASE64_ESIZE;
 	}
 
+	const uint8_t	*stop = in + in_len;
+	uint8_t		*bin = out;
+	uint8_t		pad_len = 0;
+	uint8_t		c1, c2, c3, c4;
+
 	// Decoding loop takes 4 characters and creates 3 bytes.
-	while (data < stop) {
+	while (in < stop) {
 		// Filling and transforming 4 Base64 chars.
-		c1 = base64_dec[*data++];
-		c2 = base64_dec[*data++];
-		c3 = base64_dec[*data++];
-		c4 = base64_dec[*data++];
+		c1 = base64_dec[in[0]];
+		c2 = base64_dec[in[1]];
+		c3 = base64_dec[in[2]];
+		c4 = base64_dec[in[3]];
 
 		// Check 4. char if is bad or padding.
 		if (c4 >= PD) {
@@ -240,29 +206,35 @@ int32_t base64_decode(const uint8_t  *in,
 			}
 		}
 
-		// 1. and 2. chars must not be padding.
+		// Check 1. and 2. chars if are not padding.
 		if (c2 >= PD || c1 >= PD) {
 			return KNOT_BASE64_ECHAR;
 		}
 
 		// Computing of output data based on padding length.
 		switch (pad_len) {
-		// No padding => output has 3 bytess.
 		case 0:
-			*bin++ = (c1 << 2) + (c2 >> 4);
-			*bin++ = (c2 << 4) + (c3 >> 2);
-			*bin++ = (c3 << 6) + c4;
+			bin[2] = (c3 << 6) + c4;
+		case 1:
+			bin[1] = (c2 << 4) + (c3 >> 2);
+		case 2:
+			bin[0] = (c1 << 2) + (c2 >> 4);
+		}
+
+		// Update output end.
+		switch (pad_len) {
+		case 0:
+			bin += 3;
 			break;
-		// 1-char padding => output has 2 bytes.
 		case 1:
-			*bin++ = (c1 << 2) + (c2 >> 4);
-			*bin++ = (c2 << 4) + (c3 >> 2);
+			bin += 2;
 			break;
-		// 2-char padding => output has 1 bytes.
 		case 2:
-			*bin++ = (c1 << 2) + (c2 >> 4);
+			bin += 1;
 			break;
 		}
+
+		in += 4;
 	}
 
 	return (bin - out);
diff --git a/src/libknot/internal/net.c b/src/libknot/internal/net.c
index b737d02cda9adb492d007ff99aaf400861b10e7b..58d945676d46a9089fe0a96d70f52a0327639e00 100644
--- a/src/libknot/internal/net.c
+++ b/src/libknot/internal/net.c
@@ -89,7 +89,7 @@ int net_bound_socket(int type, const struct sockaddr_storage *ss)
 
 	/* Convert to string address format. */
 	char addr_str[SOCKADDR_STRLEN] = {0};
-	sockaddr_tostr(ss, addr_str, sizeof(addr_str));
+	sockaddr_tostr(addr_str, sizeof(addr_str), ss);
 
 	/* Reuse old address if taken. */
 	int flag = 1;
diff --git a/src/libknot/internal/sockaddr.c b/src/libknot/internal/sockaddr.c
index 65acb7432c7f9e5db2084d5686fd2a5c7109f97b..cebee188805363e4dc43db27fbb6e647e6b22bdb 100644
--- a/src/libknot/internal/sockaddr.c
+++ b/src/libknot/internal/sockaddr.c
@@ -83,6 +83,25 @@ int sockaddr_set(struct sockaddr_storage *ss, int family, const char *straddr, i
 	return KNOT_EINVAL;
 }
 
+void *sockaddr_raw(struct sockaddr_storage *ss, size_t *addr_size)
+{
+	if (ss == NULL || addr_size == NULL) {
+		return NULL;
+	}
+
+	if (ss->ss_family == AF_INET) {
+		struct sockaddr_in *ipv4 = (struct sockaddr_in *)ss;
+		*addr_size = sizeof(ipv4->sin_addr);
+		return &ipv4->sin_addr;
+	} else if (ss->ss_family == AF_INET6) {
+		struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)ss;
+		*addr_size = sizeof(ipv6->sin6_addr);
+		return &ipv6->sin6_addr;
+	} else {
+		return NULL;
+	}
+}
+
 int sockaddr_set_raw(struct sockaddr_storage *ss, int family,
                      const uint8_t *raw_addr, size_t raw_addr_size)
 {
@@ -90,18 +109,10 @@ int sockaddr_set_raw(struct sockaddr_storage *ss, int family,
 		return KNOT_EINVAL;
 	}
 
-	void *sa_data = NULL;
-	size_t sa_size = 0;
+	ss->ss_family = family;
 
-	if (family == AF_INET) {
-		struct sockaddr_in *ipv4 = (struct sockaddr_in *)ss;
-		sa_data = &ipv4->sin_addr;
-		sa_size = sizeof(ipv4->sin_addr);
-	} else if (family == AF_INET6) {
-		struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)ss;
-		sa_data = &ipv6->sin6_addr;
-		sa_size = sizeof(ipv6->sin6_addr);
-	}
+	size_t sa_size = 0;
+	void *sa_data = sockaddr_raw(ss, &sa_size);
 
 	if (sa_data == NULL || sa_size != raw_addr_size) {
 		return KNOT_EINVAL;
@@ -114,7 +125,7 @@ int sockaddr_set_raw(struct sockaddr_storage *ss, int family,
 	return KNOT_EOK;
 }
 
-int sockaddr_tostr(const struct sockaddr_storage *ss, char *buf, size_t maxlen)
+int sockaddr_tostr(char *buf, size_t maxlen, const struct sockaddr_storage *ss)
 {
 	if (ss == NULL || buf == NULL) {
 		return KNOT_EINVAL;
@@ -143,17 +154,19 @@ int sockaddr_tostr(const struct sockaddr_storage *ss, char *buf, size_t maxlen)
 	}
 
 	/* Write separator and port. */
+	int written = strlen(buf);
 	int port = sockaddr_port(ss);
 	if (port > 0) {
-		size_t written = strlen(buf);
 		int ret = snprintf(&buf[written], maxlen - written, "@%d", port);
 		if (ret <= 0 || (size_t)ret >= maxlen - written) {
 			*buf = '\0';
 			return KNOT_ESPACE;
 		}
+
+		written += ret;
 	}
 
-	return KNOT_EOK;
+	return written;
 }
 
 int sockaddr_port(const struct sockaddr_storage *ss)
diff --git a/src/libknot/internal/sockaddr.h b/src/libknot/internal/sockaddr.h
index 5b3833cd563381fef8aaffc3ca24490d0e6b8429..d785c90e33b4410b2e325b5ece567b1e687dc4f7 100644
--- a/src/libknot/internal/sockaddr.h
+++ b/src/libknot/internal/sockaddr.h
@@ -75,6 +75,15 @@ int sockaddr_cmp(const struct sockaddr_storage *k1, const struct sockaddr_storag
  */
 int sockaddr_set(struct sockaddr_storage *ss, int family, const char *straddr, int port);
 
+/*!
+ * \brief Return raw network address in network byte order.
+ *
+ * \param ss Socket address storage.
+ * \param addr_size Length of the address will be stored in addr_size.
+ * \return pointer to raw address
+ */
+void *sockaddr_raw(struct sockaddr_storage *ss, size_t *addr_size);
+
 /*!
  * \brief Set raw address.
  *
@@ -83,7 +92,7 @@ int sockaddr_set(struct sockaddr_storage *ss, int family, const char *straddr, i
  * \param raw_addr IP address in binary format.
  * \param raw_addr_size Size of the binary address.
  *
- * return KNOT_EOK on success or an error code
+ * \return KNOT_EOK on success or an error code
  */
 int sockaddr_set_raw(struct sockaddr_storage *ss, int family,
                      const uint8_t *raw_addr, size_t raw_addr_size);
@@ -97,9 +106,9 @@ int sockaddr_set_raw(struct sockaddr_storage *ss, int family,
  * \param buf Destination for string representation.
  * \param maxlen Maximum number of written bytes.
  *
- * \return EOK on success, error code on failure
+ * \return Number of bytes written on success, error code on failure
  */
-int sockaddr_tostr(const struct sockaddr_storage *ss, char *buf, size_t maxlen);
+int sockaddr_tostr(char *buf, size_t maxlen, const struct sockaddr_storage *ss);
 
 /*!
  * \brief Return port number from address.
diff --git a/src/utils/common/netio.c b/src/utils/common/netio.c
index 82890ae304a4436740b00d767e780af61bcef7ee..b88abe5ba99332fe9b9f03212e2413a50c4f53ae 100644
--- a/src/utils/common/netio.c
+++ b/src/utils/common/netio.c
@@ -140,7 +140,7 @@ void get_addr_str(const struct sockaddr_storage *ss,
 	char addr_str[SOCKADDR_STRLEN] = {0};
 
 	// Get network address string and port number.
-	sockaddr_tostr(ss, addr_str, sizeof(addr_str));
+	sockaddr_tostr(addr_str, sizeof(addr_str), ss);
 
 	// Calculate needed buffer size
 	const char *sock_name = get_sockname(socktype);