diff --git a/Makefile b/Makefile
index 3a69c9aab4d9a76daaa19d49c8c501407954c41e..93e16e43c64c11cfcf2e9dde08ff23765995f121 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ include platform.mk
 all: info lib daemon modules
 install: lib-install daemon-install modules-install etc-install
 check: all tests
-clean: lib-clean daemon-clean modules-clean tests-clean doc-clean
+clean: contrib-clean lib-clean daemon-clean modules-clean tests-clean doc-clean
 doc: doc-html
 .PHONY: all install check clean doc info
 
@@ -49,7 +49,9 @@ BUILD_CFLAGS += $(addprefix -I,$(wildcard contrib/ccan/*) contrib/murmurhash3)
 info:
 	$(info Target:     Knot DNS Resolver $(MAJOR).$(MINOR).$(PATCH)-$(PLATFORM))
 	$(info Compiler:   $(CC) $(BUILD_CFLAGS))
-	$(info Linker:     $(LD) $(BUILD_LDFLAGS))
+	$(info HARDENING:  $(HARDENING))
+	$(info BUILDMODE:  $(BUILDMODE))
+	$(info PREFIX:     $(PREFIX))
 	$(info PREFIX:     $(PREFIX))
 	$(info DESTDIR:    $(DESTDIR))
 	$(info BINDIR:     $(BINDIR))
@@ -72,7 +74,6 @@ info:
 	$(info [$(HAS_libmemcached)] libmemcached (modules/memcached))
 	$(info [$(HAS_hiredis)] hiredis (modules/redis))
 	$(info [$(HAS_cmocka)] cmocka (tests/unit))
-	$(info [$(HAS_socket_wrapper)] socket_wrapper (lib))
 	$(info )
 
 # Installation directories
@@ -82,6 +83,7 @@ $(DESTDIR)$(ETCDIR):
 	$(INSTALL) -m 0750 -d $@
 
 # Sub-targets
+include contrib/contrib.mk
 include lib/lib.mk
 include daemon/daemon.mk
 include modules/modules.mk
diff --git a/config.mk b/config.mk
index 5b99b347854e1be330a335e49d1bd0f60d2f7726..33117bd6c61996ecc766fb49ab51c23c4a7c98d1 100644
--- a/config.mk
+++ b/config.mk
@@ -2,6 +2,9 @@
 MAJOR := 1
 MINOR := 0
 PATCH := 0-beta2
+ABIVER := 1
+BUILDMODE := dynamic
+HARDENING := yes
 
 # Paths
 PREFIX := /usr/local
@@ -13,10 +16,12 @@ ETCDIR := $(PREFIX)/etc/kresd
 
 # Tools
 CC	?= cc
-BUILD_LDFLAGS += $(LDFLAGS)
-BUILD_CFLAGS := $(CFLAGS) -std=c99 -D_GNU_SOURCE -fPIC -Wtype-limits -Wall -I$(abspath .) -I$(abspath lib/generic) -I$(abspath contrib)
-BUILD_CFLAGS += -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR).$(PATCH)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\"" -DETCDIR="\"$(ETCDIR)\""
 RM	:= rm -f
 LN      := ln -s
 XXD     := ./scripts/embed.sh
 INSTALL := install
+
+# Flags
+BUILD_LDFLAGS += $(LDFLAGS)
+BUILD_CFLAGS := $(CFLAGS) -std=c99 -D_GNU_SOURCE -D_FORTIFY_SOURCE=2 -Wno-unused -Wtype-limits -Wformat -Wformat-security -Wall -I$(abspath .) -I$(abspath lib/generic) -I$(abspath contrib)
+BUILD_CFLAGS += -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR).$(PATCH)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\"" -DETCDIR="\"$(ETCDIR)\""
diff --git a/contrib/cleanup.h b/contrib/cleanup.h
new file mode 100644
index 0000000000000000000000000000000000000000..7dcd750b0f3ce9844510945f98fb49a43ae7f122
--- /dev/null
+++ b/contrib/cleanup.h
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2015 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/>.
+*/
+/**
+ * Cleanup attributes.
+ * @cond internal
+ */
+#pragma once
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#define auto_free __attribute__((cleanup(_cleanup_free)))
+static inline void _cleanup_free(char **p) {
+	free(*p);
+}
+#define auto_close __attribute__((cleanup(_cleanup_close)))
+static inline void _cleanup_close(int *p) {
+	if (*p > 0) close(*p);
+}
+#define auto_fclose __attribute__((cleanup(_cleanup_fclose)))
+static inline void _cleanup_fclose(FILE **p) {
+	if (*p) fclose(*p);
+}
+/* @endcond */
\ No newline at end of file
diff --git a/contrib/contrib.mk b/contrib/contrib.mk
new file mode 100644
index 0000000000000000000000000000000000000000..3daf43df53f5eeb163856ea91867c9330df1c1e6
--- /dev/null
+++ b/contrib/contrib.mk
@@ -0,0 +1,10 @@
+contrib_SOURCES := \
+	contrib/ccan/asprintf/asprintf.c \
+	contrib/ccan/ilog/ilog.c \
+	contrib/ccan/isaac/isaac.c \
+	contrib/ccan/json/json.c \
+	contrib/ucw/mempool.c \
+	contrib/murmurhash3/murmurhash3.c
+contrib_CFLAGS := -fPIC
+contrib_TARGET := $(abspath contrib)/contrib$(AREXT)
+$(eval $(call make_static,contrib,contrib))
diff --git a/daemon/README.rst b/daemon/README.rst
index 4fccf6f66eac9c06efe7fa08f2224d29d398fec5..a93dbd7b71fbfedc171d35472eba1041fee6d3c7 100644
--- a/daemon/README.rst
+++ b/daemon/README.rst
@@ -155,6 +155,8 @@ Configuration example
    -- 10MB cache
    cache.size = 10*MB
 
+.. tip:: There are more configuration examples in `etc/` directory for personal, ISP, company internal and resolver cluster use cases.
+
 Configuration syntax
 --------------------
 
diff --git a/daemon/bindings.c b/daemon/bindings.c
index 9c751146a1ad73fb072828d746cf353363554cbb..e97230c91af78593f598024f9f298fc97b28ca85 100644
--- a/daemon/bindings.c
+++ b/daemon/bindings.c
@@ -15,6 +15,7 @@
  */
 
 #include <uv.h>
+#include <contrib/cleanup.h>
 #include <libknot/descriptor.h>
 
 #include "lib/cache.h"
diff --git a/daemon/daemon.mk b/daemon/daemon.mk
index e56fad1f79e121ccb2c7575548be74e7bbd53588..c96c1e46de7eecebdc3bf84da6087084e0be5a06 100644
--- a/daemon/daemon.mk
+++ b/daemon/daemon.mk
@@ -1,7 +1,4 @@
-kresd_EMBED := \
-	contrib/ccan/asprintf/asprintf.c
 kresd_SOURCES := \
-	$(kresd_EMBED)   \
 	daemon/io.c          \
 	daemon/network.c     \
 	daemon/engine.c      \
@@ -9,6 +6,7 @@ kresd_SOURCES := \
 	daemon/bindings.c    \
 	daemon/ffimodule.c   \
 	daemon/main.c
+
 kresd_DIST := daemon/lua/kres.lua daemon/lua/trust_anchors.lua
 
 # Embedded resources
@@ -24,8 +22,9 @@ endif
 bindings-install: $(kresd_DIST) $(DESTDIR)$(MODULEDIR)
 	$(INSTALL) -m 0644 $(kresd_DIST) $(DESTDIR)$(MODULEDIR)
 
-kresd_DEPEND := $(libkres)
-kresd_LIBS := $(libkres_TARGET) $(libknot_LIBS) $(libdnssec_LIBS) $(libuv_LIBS) $(lua_LIBS)
+kresd_CFLAGS := -fPIE
+kresd_DEPEND := $(libkres) $(contrib)
+kresd_LIBS := $(libkres_TARGET) $(contrib_TARGET) $(libknot_LIBS) $(libdnssec_LIBS) $(libuv_LIBS) $(lua_LIBS)
 
 # Make binary
 ifeq ($(HAS_lua)|$(HAS_libuv), yes|yes)
diff --git a/daemon/engine.c b/daemon/engine.c
index ac52ba0593fb557069f7110a42c8352c1dae2b8c..fcdca1aad89837f6154bde5f3efcaa59fca1dfe2 100644
--- a/daemon/engine.c
+++ b/daemon/engine.c
@@ -14,6 +14,7 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <contrib/cleanup.h>
 #include <ccan/json/json.h>
 #include <ccan/asprintf/asprintf.h>
 #include <uv.h>
@@ -153,9 +154,9 @@ static int l_quit(lua_State *L)
 static int l_verbose(lua_State *L)
 {
 	if (lua_isboolean(L, 1) || lua_isnumber(L, 1)) {
-		log_debug_enable(lua_toboolean(L, 1));
+		kr_debug_set(lua_toboolean(L, 1));
 	}
-	lua_pushboolean(L, log_debug_status());
+	lua_pushboolean(L, kr_debug_status());
 	return 1;
 }
 
@@ -176,7 +177,7 @@ static int l_option(lua_State *L)
 	unsigned opt_code = 0;
 	if (lua_isstring(L, 1)) {
 		const char *opt = lua_tostring(L, 1);
-		for (const lookup_table_t *it = query_flag_names; it->name; ++it) {
+		for (const lookup_table_t *it = kr_query_flag_names(); it->name; ++it) {
 			if (strcmp(it->name, opt) == 0) {
 				opt_code = it->id;
 				break;
diff --git a/daemon/main.c b/daemon/main.c
index ba653431e93b9c905b4bd724c76d055ddca2c194..62375a40e30bdd8d1a7bd090e729703fb62e731d 100644
--- a/daemon/main.c
+++ b/daemon/main.c
@@ -18,9 +18,10 @@
 #include <string.h>
 #include <getopt.h>
 #include <uv.h>
+#include <contrib/cleanup.h>
+#include <contrib/ucw/mempool.h>
+#include <contrib/ccan/asprintf/asprintf.h>
 
-#include "contrib/ucw/mempool.h"
-#include "contrib/ccan/asprintf/asprintf.h"
 #include "lib/defines.h"
 #include "lib/resolve.h"
 #include "lib/dnssec.h"
@@ -235,12 +236,12 @@ int main(int argc, char **argv)
 			g_interactive = 0;
 			forks = atoi(optarg);
 			if (forks == 0) {
-				log_error("[system] error '-f' requires number, not '%s'\n", optarg);
+				kr_log_error("[system] error '-f' requires number, not '%s'\n", optarg);
 				return EXIT_FAILURE;
 			}
 #if (!defined(UV_VERSION_HEX)) || (!defined(SO_REUSEPORT))
 			if (forks > 1) {
-				log_error("[system] libuv 1.7+ is required for SO_REUSEPORT support, multiple forks not supported\n");
+				kr_log_error("[system] libuv 1.7+ is required for SO_REUSEPORT support, multiple forks not supported\n");
 				return EXIT_FAILURE;
 			}
 #endif
@@ -268,15 +269,15 @@ int main(int argc, char **argv)
 			}
 			free(keyfile_buf);
 			if (!keyfile) {
-				log_error("[system] keyfile '%s': not writeable\n", optarg);
+				kr_log_error("[system] keyfile '%s': not writeable\n", optarg);
 				return EXIT_FAILURE;
 			}
 			break;
 		case 'v':
-			log_debug_enable(true);
+			kr_debug_set(true);
 			break;
 		case 'V':
-			log_info("%s, version %s\n", "Knot DNS Resolver", PACKAGE_VERSION);
+			kr_log_info("%s, version %s\n", "Knot DNS Resolver", PACKAGE_VERSION);
 			return EXIT_SUCCESS;
 		case 'h':
 		case '?':
@@ -292,17 +293,17 @@ int main(int argc, char **argv)
 	if (optind < argc) {
 		const char *rundir = argv[optind];
 		if (access(rundir, W_OK) != 0) {
-			log_error("[system] rundir '%s': %s\n", rundir, strerror(errno));
+			kr_log_error("[system] rundir '%s': %s\n", rundir, strerror(errno));
 			return EXIT_FAILURE;
 		}
 		ret = chdir(rundir);
 		if (ret != 0) {
-			log_error("[system] rundir '%s': %s\n", rundir, strerror(errno));
+			kr_log_error("[system] rundir '%s': %s\n", rundir, strerror(errno));
 			return EXIT_FAILURE;
 		}
 		if(config && access(config, R_OK) != 0) {
-			log_error("[system] rundir '%s'\n", rundir);
-			log_error("[system] config '%s': %s\n", config, strerror(errno));
+			kr_log_error("[system] rundir '%s'\n", rundir);
+			kr_log_error("[system] config '%s': %s\n", config, strerror(errno));
 			return EXIT_FAILURE;
 		}
 	}
@@ -339,13 +340,13 @@ int main(int argc, char **argv)
 	struct engine engine;
 	ret = engine_init(&engine, &pool);
 	if (ret != 0) {
-		log_error("[system] failed to initialize engine: %s\n", kr_strerror(ret));
+		kr_log_error("[system] failed to initialize engine: %s\n", kr_strerror(ret));
 		return EXIT_FAILURE;
 	}
 	/* Create worker */
 	struct worker_ctx *worker = init_worker(loop, &engine, &pool, forks, fork_count);
 	if (!worker) {
-		log_error("[system] not enough memory\n");
+		kr_log_error("[system] not enough memory\n");
 		return EXIT_FAILURE;
 	}
 	/* Bind to sockets and run */
@@ -354,7 +355,7 @@ int main(int argc, char **argv)
 		const char *addr = set_addr(addr_set.at[i], &port);
 		ret = network_listen(&engine.net, addr, (uint16_t)port, NET_UDP|NET_TCP);
 		if (ret != 0) {
-			log_error("[system] bind to '%s#%d' %s\n", addr, port, knot_strerror(ret));
+			kr_log_error("[system] bind to '%s#%d' %s\n", addr, port, knot_strerror(ret));
 			ret = EXIT_FAILURE;
 		}
 	}
@@ -365,7 +366,7 @@ int main(int argc, char **argv)
 			if (keyfile) {
 				auto_free char *cmd = afmt("trust_anchors.file = '%s'", keyfile);
 				if (!cmd) {
-					log_error("[system] not enough memory\n");
+					kr_log_error("[system] not enough memory\n");
 					return EXIT_FAILURE;
 				}
 				engine_cmd(&engine, cmd);
diff --git a/daemon/worker.c b/daemon/worker.c
index cb5dd83b9dc493b3f35483a4451f06b6897dbca8..b05643a2726f2a99ad4137d704eed1fa25631e83 100644
--- a/daemon/worker.c
+++ b/daemon/worker.c
@@ -484,7 +484,7 @@ static int subreq_key(char *dst, struct qr_task *task)
 	assert(task);
 	knot_pkt_t *pkt = task->pktbuf;
 	assert(knot_wire_get_qr(pkt->wire) == false);
-	return kr_rrmap_key(dst, knot_pkt_qname(pkt), knot_pkt_qtype(pkt), knot_pkt_qclass(pkt));
+	return kr_rrkey(dst, knot_pkt_qname(pkt), knot_pkt_qtype(pkt), knot_pkt_qclass(pkt));
 }
 
 static void subreq_finalize(struct qr_task *task, const struct sockaddr *packet_source, knot_pkt_t *pkt)
@@ -498,7 +498,7 @@ static void subreq_finalize(struct qr_task *task, const struct sockaddr *packet_
 	/* Clear from outstanding table. */
 	if (!task->leading)
 		return;
-	char key[RRMAP_KEYSIZE];
+	char key[KR_RRKEY_LEN];
 	int ret = subreq_key(key, task);
 	if (ret > 0) {
 		assert(map_get(&task->worker->outstanding, key) == task);
@@ -525,7 +525,7 @@ static void subreq_finalize(struct qr_task *task, const struct sockaddr *packet_
 static void subreq_lead(struct qr_task *task)
 {
 	assert(task);
-	char key[RRMAP_KEYSIZE];
+	char key[KR_RRKEY_LEN];
 	if (subreq_key(key, task) > 0) {
 		assert(map_contains(&task->worker->outstanding, key) == false);
 		map_set(&task->worker->outstanding, key, task);
@@ -536,12 +536,12 @@ static void subreq_lead(struct qr_task *task)
 static bool subreq_enqueue(struct qr_task *task)
 {
 	assert(task);
-	char key[RRMAP_KEYSIZE];
+	char key[KR_RRKEY_LEN];
 	if (subreq_key(key, task) > 0) {
 		struct qr_task *leader = map_get(&task->worker->outstanding, key);
 		if (leader) {
 			/* Enqueue itself to leader for this subrequest. */
-			int ret = array_reserve_mm(leader->waiting, leader->waiting.len + 1, mm_reserve, &leader->req.pool);
+			int ret = array_reserve_mm(leader->waiting, leader->waiting.len + 1, kr_memreserve, &leader->req.pool);
 			if (ret == 0) {
 				array_push(leader->waiting, task);
 				qr_task_ref(task);
diff --git a/doc/build.rst b/doc/build.rst
index 55d93032b6852077e6d9d5f488d27d853cdbbe27..9d0dccb882254649f0762e37b27fc6c19c689049 100644
--- a/doc/build.rst
+++ b/doc/build.rst
@@ -78,24 +78,6 @@ Most of the dependencies can be resolved from packages, here's an overview for s
 
    brew install pkg-config libuv luajit cmocka
 
-Getting Docker image
---------------------
-
-Docker images require only either Linux or a Linux VM (see boot2docker_ on OS X).
-
-.. code-block:: bash
-
-   $ docker run cznic/knot-resolver
-
-See the `Docker images`_ page for more information and options.
-You can hack on the container by changing the container entrypoint to shell like:
-
-.. code-block:: bash
-
-   $ docker run -it --entrypoint=/bin/bash cznic/knot-resolver
-
-.. tip:: You can build the Docker image yourself with ``docker build -t knot-resolver scripts``.
-
 Building from sources 
 ---------------------
 
@@ -123,6 +105,24 @@ Alternatively you can build only specific parts of the project, i.e. ``library``
 
 .. note:: Documentation is not built by default, run ``make doc`` to build it.
 
+Building with security compiler flags
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Knot DNS Resolver enables certain `security compile-time flags <https://wiki.debian.org/Hardening#Notes_on_Memory_Corruption_Mitigation_Methods>`_ that do not affect performance.
+You can add more flags to the build by appending them to `CFLAGS` variable, e.g. ``make CFLAGS="-fstack-protector"``.
+
+  .. csv-table::
+   :header: "Method", "Status", "Notes"
+
+   "-fstack-protector", "*disabled*", "(must be specifically enabled in CFLAGS)"
+   "-D_FORTIFY_SOURCE=2", "**enabled**", ""
+   "-pie", "**enabled**", "enables ASLR for kresd (disable with ``make HARDENING=no``)"
+   "RELRO", "**enabled**", "full [#]_"
+
+You can also disable linker hardening when it's unsupported with ``make HARDENING=no``.
+
+.. [#] See `checksec.sh <http://www.trapkit.de/tools/checksec.html>`_
+
 Building for packages
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -133,7 +133,41 @@ The build system supports both DESTDIR_ and `amalgamated builds <https://www.sql
    $ make install DESTDIR=/tmp/stage # Staged install
    $ make all install AMALG=yes # Amalgamated build
 
-.. note:: Amalgamated build assembles everything in one source file and compiles it. It is useful for packages, as the compiler sees the whole program and is able to produce a smaller and faster binary. On the other hand, it complicates debugging.
+Amalgamated build assembles everything in one source file and compiles it. It is useful for packages, as the compiler sees the whole program and is able to produce a smaller and faster binary. On the other hand, it complicates debugging.
+
+.. tip:: There is a template for service file and AppArmor profile to help you kickstart the package.
+
+Default paths
+~~~~~~~~~~~~~
+
+The default installation follows FHS with several custom paths for configuration and modules.
+All paths are prefixed with ``PREFIX`` variable by default if not specified otherwise.
+
+  .. csv-table::
+   :header: "Component", "Variable", "Default", "Notes"
+
+   "library", "``LIBDIR``", "``$(PREFIX)/lib``", "pkg-config is auto-generated [#]_"
+   "daemon",  "``BINDIR``", "``$(PREFIX)/bin``", ""
+   "configuration", "``ETCDIR``", "``$(PREFIX)/etc/kresd``", "Configuration file, templates."
+   "modules", "``MODULEDIR``", "``$(LIBDIR)/kdns_modules``", "[#]_"
+   "work directory", "", "``$(PREFIX)/var/run/kresd``", "Run directory for daemon."
+
+.. [#] The ``libkres.pc`` is installed in ``$(LIBDIR)/pkgconfig``.
+.. [#] Users may install additional modules in ``~/.local/lib/kdns_modules`` or in the rundir of a specific instance.
+
+.. note:: Each module is self-contained and may install additional bundled files within ``$(MODULEDIR)/$(modulename)``. These files should be read-only, non-executable.
+
+Static or dynamic?
+~~~~~~~~~~~~~~~~~~
+
+By default the resolver library is built as a dynamic library with versioned ABI. You can revert to static build with ``BUILDMODE`` variable.
+
+.. code-block:: bash
+
+   $ make BUILDMODE=dynamic # Default, create dynamic library
+   $ make BUILDMODE=static  # Create static library
+
+When the library is linked statically, it usually produces a smaller binary. However linking it to various C modules might violate ODR and increase the size. 
 
 Building dependencies
 ~~~~~~~~~~~~~~~~~~~~~
@@ -181,6 +215,24 @@ is otherwise unable to find and load modules.
 
 Read the `documentation <deckard_doc>`_ for more information about requirements, how to run it and extend it.
 
+Getting Docker image
+--------------------
+
+Docker images require only either Linux or a Linux VM (see boot2docker_ on OS X).
+
+.. code-block:: bash
+
+   $ docker run cznic/knot-resolver
+
+See the `Docker images`_ page for more information and options.
+You can hack on the container by changing the container entrypoint to shell like:
+
+.. code-block:: bash
+
+   $ docker run -it --entrypoint=/bin/bash cznic/knot-resolver
+
+.. tip:: You can build the Docker image yourself with ``docker build -t knot-resolver scripts``.
+
 .. _Docker images: https://registry.hub.docker.com/u/cznic/knot-resolver
 .. _libuv: https://github.com/libuv/libuv
 .. _MSVC: https://msdn.microsoft.com/en-us/vstudio/hh386302.aspx
diff --git a/etc/config.cluster b/etc/config.cluster
new file mode 100644
index 0000000000000000000000000000000000000000..5be5c923ca4ca4f07058aee4bb254703191366f0
--- /dev/null
+++ b/etc/config.cluster
@@ -0,0 +1,46 @@
+-- Config file example useable for larger resolver farms
+-- In this case cache should be made as large as possible, and prefetching turned off
+-- as the resolver is busy most of the time.
+-- Alternative is using `etcd` as a configuration backend.
+-- Refer to manual: http://knot-resolver.readthedocs.org/en/latest/daemon.html#configuration
+
+-- Listen on localhost and external interface
+net = { '127.0.0.1', '::1', '192.168.1.1' }
+
+-- Drop root privileges
+user('kresd', 'kresd')
+
+-- Auto-maintain root TA
+trust_anchors.file = 'root.keys'
+
+-- Large cache size, so we don't need to flush ever
+-- This can be larger than available RAM, least frequently accessed
+-- records will be paged out as long as there's enough disk space to back it
+cache.size = 100 * GB 
+
+-- Load Useful modules
+modules = {
+	'policy',   -- Block queries to local zones/bad sites
+	'cachectl', -- Cache control interface
+	'hints',    -- Load /etc/hosts and allow custom root hints
+	'stats',    -- Track internal statistics
+	graphite = { -- Send statistics to local InfluxDB
+		-- `worker.id` allows us to keep per-fork statistics
+		prefix = hostname()..worker.id,
+		-- Address of the Graphite/InfluxDB server
+		host = '192.168.1.2',
+	},
+	'redis',   -- Allow to use Redis as a cache backend
+}
+
+-- Use local root server copy for performance reasons
+hints.root({
+  ['j.root-servers.net.'] = { '192.168.1.4', '2001:503:c27::2:30', '192.58.128.30' }
+})
+
+-- Apply RPZ for all clients, default rule is DENY
+policy:add(policy.rpz(policy.DENY, 'blacklist.rpz'))
+
+-- Optional: use Redis/Memcached as a cache backend which may be distributed between nodes
+-- cache.storage = 'redis://127.0.0.1:6398'
+
diff --git a/etc/config.isp b/etc/config.isp
new file mode 100644
index 0000000000000000000000000000000000000000..4d6e9910bb9ed4986f2c7e4afa17b4bdbe0ff7b1
--- /dev/null
+++ b/etc/config.isp
@@ -0,0 +1,39 @@
+-- Config file example useable for multi-user ISP resolver
+-- Refer to manual: http://knot-resolver.readthedocs.org/en/latest/daemon.html#configuration
+
+-- Listen on localhost and external interface
+net = { '127.0.0.1', '::1', '192.168.1.1' }
+
+-- Drop root privileges
+user('kresd', 'kresd')
+
+-- Auto-maintain root TA
+trust_anchors.file = 'root.keys'
+
+-- Large cache size, so we don't need to flush often
+-- This can be larger than available RAM, least frequently accessed
+-- records will be paged out
+cache.size = 4 * GB 
+
+-- Load Useful modules
+modules = {
+	'policy',   -- Block queries to local zones/bad sites
+	'view',     -- Views for certain clients
+	'cachectl', -- Cache control interface
+	'hints',    -- Load /etc/hosts and allow custom root hints
+	'stats',    -- Track internal statistics
+	graphite = { -- Send statistics to local InfluxDB
+		-- `worker.id` allows us to keep per-fork statistics
+		prefix = hostname()..worker.id,
+		-- Address of the Graphite/InfluxDB server
+		host = '192.168.1.2',
+	}
+}
+
+-- Block all `site.nl` for `10.0.0.0/24` subnet
+view:addr('10.0.0.0/24', policy.suffix(policy.DROP, {todname('site.nl')}))
+-- Force all clients from `192.168.2.0/24` to TCP
+view:addr('192.168.2.0/24', policy.all(policy.TC))
+-- Apply RPZ for all clients, default rule is DENY
+policy:add(policy.rpz(policy.DENY, 'blacklist.rpz'))
+
diff --git a/etc/config.personal b/etc/config.personal
new file mode 100644
index 0000000000000000000000000000000000000000..12881b13df33d318b8ab71891bd5f53d692534e8
--- /dev/null
+++ b/etc/config.personal
@@ -0,0 +1,25 @@
+-- Config file example useable for personal resolver.
+-- The goal is to have a validating resolver with tiny memory footprint,
+-- while actively tracking and refreshing frequent records to lower user latency.
+-- Refer to manual: http://knot-resolver.readthedocs.org/en/latest/daemon.html#configuration
+
+-- Listen on localhost (default)
+-- net = { '127.0.0.1', '::1' }
+
+-- Drop root privileges
+user('kresd', 'kresd')
+
+-- Auto-maintain root TA
+trust_anchors.file = 'root.keys'
+
+-- Load Useful modules
+modules = {
+	'policy',   -- Block queries to local zones/bad sites
+	'cachectl', -- Cache control interface
+	'hints',    -- Load /etc/hosts and allow custom root hints
+	'stats',    -- Track internal statistics
+	'predict',  -- Prefetch expiring/frequent records
+}
+
+-- Smaller cache size
+cache.size = 10 * MB
diff --git a/etc/config.splitview b/etc/config.splitview
new file mode 100644
index 0000000000000000000000000000000000000000..ff26a09ad3aa4167b0abd4be95383bc91e526d28
--- /dev/null
+++ b/etc/config.splitview
@@ -0,0 +1,35 @@
+-- Config file with split-view for internal zone
+-- Refer to manual: http://knot-resolver.readthedocs.org/en/latest/daemon.html#configuration
+
+-- Listen on localhost and external interface
+net = { '127.0.0.1', '::1', '192.168.1.1' }
+
+-- Drop root privileges
+user('kresd', 'kresd')
+
+-- Auto-maintain root TA
+trust_anchors.file = 'root.keys'
+
+-- Load Useful modules
+modules = {
+	'policy',   -- Block queries to local zones/bad sites
+	'cachectl', -- Cache control interface
+	'hints',    -- Load /etc/hosts and allow custom root hints
+	'stats',    -- Track internal statistics
+	graphite = { -- Send statistics to local InfluxDB
+		-- `worker.id` allows us to keep per-fork statistics
+		prefix = hostname()..worker.id,
+		-- Address of the Graphite/InfluxDB server
+		host = '192.168.1.2',
+	},
+	-- Use DNS64 with specified NAT64 address
+	dns64 = 'fe80::21b:77ff:0:0',
+}
+
+-- Large cache size, so we don't need to flush often
+-- This can be larger than available RAM, least frequently accessed
+-- records will be paged out
+cache.size = 4 * GB 
+
+-- Forward everything below `company.cz` to `192.168.1.3`
+policy:add(policy.suffix(policy.FORWARD('192.168.1.3'), {todname('company.cz')}))
diff --git a/etc/etc.mk b/etc/etc.mk
index 18ee2a0fc5ff1b060552e3d43acd13d1977ccf30..9c5b446eb93781c785ad15b5d9b7641302b0025c 100644
--- a/etc/etc.mk
+++ b/etc/etc.mk
@@ -1,4 +1,8 @@
-etc_SOURCES := icann-ca.pem
+etc_SOURCES := icann-ca.pem \
+	config.cluster \
+	config.isp \
+	config.personal \
+	config.splitview
 
 etc-install: $(DESTDIR)$(ETCDIR)
 	$(INSTALL) -m 0640 $(addprefix etc/,$(etc_SOURCES)) $(DESTDIR)$(ETCDIR)
diff --git a/lib/cache.c b/lib/cache.c
index 64394bd3ed6e8e0fb4e4dd679e69b819353f4b67..50347f7220d796f20bb6e2a01bd1813aca96bf4e 100644
--- a/lib/cache.c
+++ b/lib/cache.c
@@ -58,7 +58,7 @@ static void assert_right_version(struct kr_cache *cache)
 	/* Recreate cache and write version key */
 	ret = txn_api(&txn)->count(&txn.t);
 	if (ret > 0) { /* Non-empty cache, purge it. */
-		log_info("[cache] purging cache\n");
+		kr_log_info("[cache] purging cache\n");
 		kr_cache_clear(&txn);
 		kr_cache_txn_commit(&txn);
 		ret = kr_cache_txn_begin(cache, &txn, 0);
diff --git a/lib/cache.h b/lib/cache.h
index 211eb20cf5754112083a43b611409610fec8e8cb..3fabea7ced0d18e8764e2af1434a56d308d10607 100644
--- a/lib/cache.h
+++ b/lib/cache.h
@@ -18,6 +18,7 @@
 
 #include <libknot/rrset.h>
 #include <libknot/internal/namedb/namedb.h>
+#include "lib/defines.h"
 
 /** Cache entry tag */
 enum kr_cache_tag {
@@ -86,6 +87,7 @@ struct kr_cache_txn {
  * @param mm    memory context.
  * @return 0 or an error code
  */
+KR_EXPORT
 int kr_cache_open(struct kr_cache *cache, const namedb_api_t *api, void *opts, mm_ctx_t *mm);
 
 /**
@@ -93,6 +95,7 @@ int kr_cache_open(struct kr_cache *cache, const namedb_api_t *api, void *opts, m
  * @note This doesn't clear the data, just closes the connection to the database.
  * @param cache database instance
  */
+KR_EXPORT
 void kr_cache_close(struct kr_cache *cache);
 
 /**
@@ -103,6 +106,7 @@ void kr_cache_close(struct kr_cache *cache);
  * @param flags transaction flags (see namedb.h in libknot)
  * @return 0 or an errcode
  */
+KR_EXPORT
 int kr_cache_txn_begin(struct kr_cache *cache, struct kr_cache_txn *txn, unsigned flags);
 
 /**
@@ -110,12 +114,14 @@ int kr_cache_txn_begin(struct kr_cache *cache, struct kr_cache_txn *txn, unsigne
  * @param txn transaction instance
  * @return 0 or an errcode
  */
+KR_EXPORT
 int kr_cache_txn_commit(struct kr_cache_txn *txn);
 
 /**
  * Abort existing transaction instance.
  * @param txn transaction instance
  */
+KR_EXPORT
 void kr_cache_txn_abort(struct kr_cache_txn *txn);
 
 /**
@@ -129,6 +135,7 @@ void kr_cache_txn_abort(struct kr_cache_txn *txn);
  * @param timestamp current time (will be replaced with drift if successful)
  * @return 0 or an errcode
  */
+KR_EXPORT
 int kr_cache_peek(struct kr_cache_txn *txn, uint8_t tag, const knot_dname_t *name, uint16_t type,
                   struct kr_cache_entry **entry, uint32_t *timestamp);
 
@@ -144,6 +151,7 @@ int kr_cache_peek(struct kr_cache_txn *txn, uint8_t tag, const knot_dname_t *nam
  * @param data inserted data
  * @return 0 or an errcode
  */
+KR_EXPORT
 int kr_cache_insert(struct kr_cache_txn *txn, uint8_t tag, const knot_dname_t *name, uint16_t type,
                     struct kr_cache_entry *header, namedb_val_t data);
 
@@ -155,6 +163,7 @@ int kr_cache_insert(struct kr_cache_txn *txn, uint8_t tag, const knot_dname_t *n
  * @param type record type
  * @return 0 or an errcode
  */
+KR_EXPORT
 int kr_cache_remove(struct kr_cache_txn *txn, uint8_t tag, const knot_dname_t *name, uint16_t type);
 
 /**
@@ -162,6 +171,7 @@ int kr_cache_remove(struct kr_cache_txn *txn, uint8_t tag, const knot_dname_t *n
  * @param txn transaction instance
  * @return 0 or an errcode
  */
+KR_EXPORT
 int kr_cache_clear(struct kr_cache_txn *txn);
 
 /**
@@ -173,6 +183,7 @@ int kr_cache_clear(struct kr_cache_txn *txn);
  * @param timestamp current time
  * @return rank (0 or positive), or an error (negative number)
  */
+KR_EXPORT
 int kr_cache_peek_rank(struct kr_cache_txn *txn, uint8_t tag, const knot_dname_t *name, uint16_t type, uint32_t timestamp);
 
 /**
@@ -184,6 +195,7 @@ int kr_cache_peek_rank(struct kr_cache_txn *txn, uint8_t tag, const knot_dname_t
  * @param timestamp current time (will be replaced with drift if successful)
  * @return 0 or an errcode
  */
+KR_EXPORT
 int kr_cache_peek_rr(struct kr_cache_txn *txn, knot_rrset_t *rr, uint16_t *rank, uint32_t *timestamp);
 
 /**
@@ -194,6 +206,7 @@ int kr_cache_peek_rr(struct kr_cache_txn *txn, knot_rrset_t *rr, uint16_t *rank,
  * @param mm memory context
  * @return 0 or an errcode
  */
+KR_EXPORT
 int kr_cache_materialize(knot_rrset_t *dst, const knot_rrset_t *src, uint32_t drift, mm_ctx_t *mm);
 
 /**
@@ -204,6 +217,7 @@ int kr_cache_materialize(knot_rrset_t *dst, const knot_rrset_t *src, uint32_t dr
  * @param timestamp current time
  * @return 0 or an errcode
  */
+KR_EXPORT
 int kr_cache_insert_rr(struct kr_cache_txn *txn, const knot_rrset_t *rr, uint16_t rank, uint32_t timestamp);
 
 /**
@@ -215,6 +229,7 @@ int kr_cache_insert_rr(struct kr_cache_txn *txn, const knot_rrset_t *rr, uint16_
  * @param timestamp current time (will be replaced with drift if successful)
  * @return 0 or an errcode
  */
+KR_EXPORT
 int kr_cache_peek_rrsig(struct kr_cache_txn *txn, knot_rrset_t *rr, uint16_t *rank, uint32_t *timestamp);
 
 /**
@@ -226,4 +241,5 @@ int kr_cache_peek_rrsig(struct kr_cache_txn *txn, knot_rrset_t *rr, uint16_t *ra
  * @param timestamp current time
  * @return 0 or an errcode
  */
+KR_EXPORT
 int kr_cache_insert_rrsig(struct kr_cache_txn *txn, const knot_rrset_t *rr, uint16_t rank, uint32_t timestamp);
diff --git a/lib/defines.h b/lib/defines.h
index c70c279f980c1657884cb8d0a05ce40162e8d7ae..7031f7ddb5fe8d9fc270ae0d1ee6e7f73a9620c2 100644
--- a/lib/defines.h
+++ b/lib/defines.h
@@ -27,11 +27,13 @@
 #define KR_CONST __attribute__((__const__))
 #define KR_PURE __attribute__((__pure__))
 #define KR_NORETURN __attribute__((__noreturn__))
+#define KR_COLD __attribute__((__cold__))
 #else
 #define KR_EXPORT
 #define KR_CONST
 #define KR_PURE
 #define KR_NORETURN
+#define KR_COLD
 #endif
 
 /*
diff --git a/lib/dnssec.h b/lib/dnssec.h
index 7679f730a2a41d7a410248687b09b376bda29d3f..82ea7bccfa290a0e2970ed3091ea8b05c8d0072d 100644
--- a/lib/dnssec.h
+++ b/lib/dnssec.h
@@ -16,23 +16,27 @@
 
 #pragma once
 
+#include "lib/defines.h"
 #include <libknot/internal/consts.h>
 #include <libknot/packet/pkt.h>
 
 /**
  * Initialise cryptographic back-end.
  */
+KR_EXPORT
 void kr_crypto_init(void);
 
 /**
  * De-initialise cryptographic back-end.
  */
+KR_EXPORT
 void kr_crypto_cleanup(void);
 
 /**
  * Re-initialise cryptographic back-end.
  * @note Must be called after fork() in the child.
  */
+KR_EXPORT
 void kr_crypto_reinit(void);
 
 /** Opaque DNSSEC key pointer. */
@@ -89,12 +93,15 @@ int kr_dnskeys_trusted(const knot_pkt_t *pkt, knot_section_t section_id, const k
                        bool has_nsec3);
 
 /** Return true if the DNSKEY can be used as a ZSK.  */
+KR_EXPORT KR_PURE
 bool kr_dnssec_key_zsk(const uint8_t *dnskey_rdata);
 
 /** Return true if the DNSKEY indicates being KSK (=> has SEP).  */
+KR_EXPORT KR_PURE
 bool kr_dnssec_key_ksk(const uint8_t *dnskey_rdata);
 
 /** Return true if the DNSKEY is revoked. */
+KR_EXPORT KR_PURE
 bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata);
 
 /** Return DNSKEY tag.
@@ -103,6 +110,7 @@ bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata);
   * @param rdlen  RDATA length.
   * @return Key tag (positive number), or an error code
   */
+KR_EXPORT KR_PURE
 int kr_dnssec_key_tag(uint16_t rrtype, const uint8_t *rdata, size_t rdlen);
 
 /** Return 0 if the two keys are identical.
@@ -113,6 +121,7 @@ int kr_dnssec_key_tag(uint16_t rrtype, const uint8_t *rdata, size_t rdlen);
   * @param key_b_rdlen Second key RDATA length
   * @return 0 if they match or an error code
   */
+KR_EXPORT KR_PURE
 int kr_dnssec_key_match(const uint8_t *key_a_rdata, size_t key_a_rdlen,
                         const uint8_t *key_b_rdata, size_t key_b_rdlen);
 
diff --git a/lib/dnssec/ta.h b/lib/dnssec/ta.h
index 6c712a0db162205b985d01c66d837ea8e77f8a4d..3541c4ac4e4ab275f6c307f0751e9b45344682da 100644
--- a/lib/dnssec/ta.h
+++ b/lib/dnssec/ta.h
@@ -25,6 +25,7 @@
  * @param  name          name of the TA
  * @return non-empty RRSet or NULL
  */
+KR_EXPORT
 knot_rrset_t *kr_ta_get(map_t *trust_anchors, const knot_dname_t *name);
 
 /**
@@ -37,6 +38,7 @@ knot_rrset_t *kr_ta_get(map_t *trust_anchors, const knot_dname_t *name);
  * @param  rdlen         
  * @return 0 or an error
  */
+KR_EXPORT
 int kr_ta_add(map_t *trust_anchors, const knot_dname_t *name, uint16_t type,
                uint32_t ttl, const uint8_t *rdata, uint16_t rdlen);
 
@@ -47,6 +49,7 @@ int kr_ta_add(map_t *trust_anchors, const knot_dname_t *name, uint16_t type,
  * @param  name          name of the TA
  * @return boolean
  */
+KR_EXPORT KR_PURE
 int kr_ta_covers(map_t *trust_anchors, const knot_dname_t *name);
 
 /**
@@ -55,10 +58,12 @@ int kr_ta_covers(map_t *trust_anchors, const knot_dname_t *name);
  * @param  name          name of the TA
  * @return 0 or an error
  */
+KR_EXPORT
 int kr_ta_del(map_t *trust_anchors, const knot_dname_t *name);
 
 /**
  * Clear trust store.
  * @param trust_anchors trust store
  */
+KR_EXPORT
 void kr_ta_clear(map_t *trust_anchors);
diff --git a/lib/generic/map.c b/lib/generic/map.c
index a6a12911a2b6bd8546dfa15b04327690ef89f45f..81a283dc5c900411875a3f9e8f622d37609ae7c8 100644
--- a/lib/generic/map.c
+++ b/lib/generic/map.c
@@ -17,6 +17,13 @@
 
 #include "map.h"
 
+ /* Exports */
+#if defined _WIN32 || defined __CYGWIN__
+  #define EXPORT __attribute__ ((dllexport))
+#else
+  #define EXPORT __attribute__ ((visibility ("default")))
+#endif
+
 #ifdef _MSC_VER /* MSVC */
  typedef unsigned __int8 uint8_t;
  typedef unsigned __int32 uint32_t;
@@ -112,7 +119,7 @@ static cb_data_t *cbt_make_data(map_t *map, const uint8_t *str, size_t len, void
 }
 
 /*! Creates a new, empty critbit map */
-map_t map_make(void)
+EXPORT map_t map_make(void)
 {
 	map_t map;
 	map.root = NULL;
@@ -123,12 +130,12 @@ map_t map_make(void)
 }
 
 /*! Returns non-zero if map contains str */
-int map_contains(map_t *map, const char *str)
+EXPORT int map_contains(map_t *map, const char *str)
 {
 	return map_get(map, str) != NULL;
 }
 
-void *map_get(map_t *map, const char *str)
+EXPORT void *map_get(map_t *map, const char *str)
 {
 	const uint8_t *ubytes = (void *)str;
 	const size_t ulen = strlen(str);
@@ -161,7 +168,7 @@ void *map_get(map_t *map, const char *str)
 }
 
 /*! Inserts str into map, returns 0 on success */
-int map_set(map_t *map, const char *str, void *value)
+EXPORT int map_set(map_t *map, const char *str, void *value)
 {
 	const uint8_t *const ubytes = (void *)str;
 	const size_t ulen = strlen(str);
@@ -262,7 +269,7 @@ different_byte_found:
 }
 
 /*! Deletes str from the map, returns 0 on success */
-int map_del(map_t *map, const char *str)
+EXPORT int map_del(map_t *map, const char *str)
 {
 	const uint8_t *ubytes = (void *)str;
 	const size_t ulen = strlen(str);
@@ -307,7 +314,7 @@ int map_del(map_t *map, const char *str)
 }
 
 /*! Clears the given map */
-void map_clear(map_t *map)
+EXPORT void map_clear(map_t *map)
 {
 	if (map->root) {
 		cbt_traverse_delete(map, map->root);
@@ -316,7 +323,7 @@ void map_clear(map_t *map)
 }
 
 /*! Calls callback for all strings in map with the given prefix */
-int map_walk_prefixed(map_t *map, const char *prefix,
+EXPORT int map_walk_prefixed(map_t *map, const char *prefix,
 	int (*callback)(const char *, void *, void *), void *baton)
 {
 	const uint8_t *ubytes = (void *)prefix;
diff --git a/lib/layer.h b/lib/layer.h
index 335edadef26939727af6a77e9a6174d7e9bbaf01..3d1d536a3e602e7b9b626f64d0252edd3966d383 100644
--- a/lib/layer.h
+++ b/lib/layer.h
@@ -25,7 +25,7 @@
  #define QRDEBUG(query, cls, fmt, ...) do { \
     unsigned _ind = 0; \
     for (struct kr_query *q = (query); q; q = q->parent, _ind += 2); \
-    log_debug("[%s] %*s" fmt, cls, _ind, "", ##  __VA_ARGS__); \
+    kr_log_debug("[%s] %*s" fmt, cls, _ind, "", ##  __VA_ARGS__); \
     } while (0)
 #else
  #define QRDEBUG(query, cls, fmt, ...)
diff --git a/lib/lib.mk b/lib/lib.mk
index 9f09c58fcbada9273619af89f3c8b53d394b8bb8..822af51f78f356b1d02227c56a64caf4c8259e84 100644
--- a/lib/lib.mk
+++ b/lib/lib.mk
@@ -1,12 +1,4 @@
-ccan_EMBED := \
-	contrib/ccan/ilog/ilog.c \
-	contrib/ccan/isaac/isaac.c \
-	contrib/ccan/json/json.c \
-	contrib/ucw/mempool.c \
-	contrib/murmurhash3/murmurhash3.c
-
 libkres_SOURCES := \
-	$(ccan_EMBED)          \
 	lib/generic/map.c      \
 	lib/layer/iterate.c    \
 	lib/layer/validate.c   \
@@ -47,17 +39,36 @@ libkres_HEADERS := \
 	lib/cache.h
 
 # Dependencies
-libkres_DEPEND := 
-libkres_CFLAGS := -fvisibility=hidden
-libkres_LIBS := $(libknot_LIBS) $(libdnssec_LIBS)
+libkres_DEPEND := $(contrib)
+libkres_CFLAGS := -fvisibility=hidden -fPIC
+libkres_LIBS := $(contrib_TARGET) $(libknot_LIBS) $(libdnssec_LIBS)
 libkres_TARGET := -L$(abspath lib) -lkres
 
 # Make library
+ifeq ($(BUILDMODE), static)
 $(eval $(call make_static,libkres,lib,yes))
+else
+$(eval $(call make_lib,libkres,lib,yes,$(ABIVER)))
+endif
+
+# Generate pkg-config file
+libkres.pc:
+	@echo 'prefix='$(PREFIX) > $@
+	@echo 'exec_prefix=$${prefix}' >> $@
+	@echo 'libdir='$(LIBDIR) >> $@
+	@echo 'includedir='$(INCLUDEDIR) >> $@
+	@echo 'Name: libkres' >> $@
+	@echo 'Description: Knot DNS Resolver library' >> $@
+	@echo 'URL: https://www.knot-dns.cz' >> $@
+	@echo 'Version: $(MAJOR).$(MINOR).$(PATCH)' >> $@
+	@echo 'Libs: -L$${libdir} -lkres' >> $@
+	@echo 'Cflags: -I$${includedir}' >> $@
+libkres-pcinstall: libkres.pc libkres-install
+	$(INSTALL) -m 644 $< $(DESTDIR)$(LIBDIR)/pkgconfig
 
 # Targets
 lib: $(libkres)
-lib-install: libkres-install
+lib-install: libkres-install libkres-pcinstall
 lib-clean: libkres-clean
 
-.PHONY: lib lib-install lib-clean
+.PHONY: lib lib-install lib-clean libkres.pc
diff --git a/lib/module.c b/lib/module.c
index 369be3aa26513d4615eb0ef9c303e82f3423a2dd..cd075a8eee0c83682564e3da0378be912f64b3be 100644
--- a/lib/module.c
+++ b/lib/module.c
@@ -17,7 +17,7 @@
 #include <stdlib.h>
 #include <dlfcn.h>
 #include <pthread.h>
-#include <unistd.h>
+#include <contrib/cleanup.h>
 
 #include "lib/defines.h"
 #include "lib/utils.h"
diff --git a/lib/module.h b/lib/module.h
index d3d2c7b307421b5d38a70085b68f4909462ca45e..4b9ec2b95cbc0939834ae60a04020471b02e57cf 100644
--- a/lib/module.h
+++ b/lib/module.h
@@ -72,6 +72,7 @@ struct kr_module {
  * @param path module search path
  * @return 0 or an error
  */
+KR_EXPORT
 int kr_module_load(struct kr_module *module, const char *name, const char *path);
 
 /**
@@ -79,6 +80,7 @@ int kr_module_load(struct kr_module *module, const char *name, const char *path)
  *
  * @param module module structure
  */
+KR_EXPORT
 void kr_module_unload(struct kr_module *module);
 
 /**
diff --git a/lib/nsrep.h b/lib/nsrep.h
index dbeca487645af7be80d99bbcf28e1dda30473f61..848b28f0b542be9886ddd3c62be7f773a6997776 100644
--- a/lib/nsrep.h
+++ b/lib/nsrep.h
@@ -87,6 +87,7 @@ struct kr_nsrep
  * @param  addr_len address bytes length (type will be derived from this)
  * @return          0 or an error code
  */
+KR_EXPORT
 int kr_nsrep_set(struct kr_query *qry, uint8_t *addr, size_t addr_len);
 
 /**
@@ -95,6 +96,7 @@ int kr_nsrep_set(struct kr_query *qry, uint8_t *addr, size_t addr_len);
  * @param  ctx          resolution context
  * @return              0 or an error code
  */
+KR_EXPORT
 int kr_nsrep_elect(struct kr_query *qry, struct kr_context *ctx);
 
 /**
@@ -103,6 +105,7 @@ int kr_nsrep_elect(struct kr_query *qry, struct kr_context *ctx);
  * @param  ctx          resolution context
  * @return              0 or an error code
  */
+KR_EXPORT
 int kr_nsrep_elect_addr(struct kr_query *qry, struct kr_context *ctx);
 
 /**
@@ -116,6 +119,7 @@ int kr_nsrep_elect_addr(struct kr_query *qry, struct kr_context *ctx);
  * @param  cache        LRU cache
  * @return              0 on success, error code on failure
  */
+KR_EXPORT
 int kr_nsrep_update_rtt(struct kr_nsrep *ns, const struct sockaddr *addr, unsigned score, kr_nsrep_lru_t *cache);
 
 /**
@@ -126,4 +130,5 @@ int kr_nsrep_update_rtt(struct kr_nsrep *ns, const struct sockaddr *addr, unsign
  * @param  cache        LRU cache
  * @return              0 on success, error code on failure
  */
+KR_EXPORT
 int kr_nsrep_update_rep(struct kr_nsrep *ns, unsigned reputation, kr_nsrep_lru_t *cache);
diff --git a/lib/resolve.c b/lib/resolve.c
index b665345ee082ad0be907ae750f70965619380c74..df7c91ff9d427e4cdd8f6fb59e0ae78df1850d56 100644
--- a/lib/resolve.c
+++ b/lib/resolve.c
@@ -111,9 +111,10 @@ static int invalidate_ns(struct kr_rplan *rplan, struct kr_query *qry)
 	if (qry->ns.addr[0].ip.sa_family != AF_UNSPEC) {
 		uint8_t *addr = kr_nsrep_inaddr(qry->ns.addr[0]);
 		size_t addr_len = kr_nsrep_inaddr_len(qry->ns.addr[0]);
-		knot_rdata_t rdata[knot_rdata_array_size(addr_len)];
-		knot_rdata_init(rdata, addr_len, addr, 0);
-		return kr_zonecut_del(&qry->zone_cut, qry->ns.name, rdata);
+		/* @warning _NOT_ thread-safe */
+		static knot_rdata_t rdata_arr[RDATA_ARR_MAX];
+		knot_rdata_init(rdata_arr, addr_len, addr, 0);
+		return kr_zonecut_del(&qry->zone_cut, qry->ns.name, rdata_arr);
 	} else {
 		return kr_zonecut_del(&qry->zone_cut, qry->ns.name, NULL);
 	}
diff --git a/lib/resolve.h b/lib/resolve.h
index 20d589d5bb7c0c68a45211665232902681f0afb7..905e134e003c123b5cd613871f6e695e8304219a 100644
--- a/lib/resolve.h
+++ b/lib/resolve.h
@@ -132,6 +132,7 @@ struct kr_request {
  * @param answer  allocated packet for final answer
  * @return        CONSUME (expecting query)
  */
+KR_EXPORT
 int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx, knot_pkt_t *answer);
 
 /**
@@ -144,6 +145,7 @@ int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx, knot_pk
  * @param  packet  [in] input packet
  * @return         any state
  */
+KR_EXPORT
 int kr_resolve_consume(struct kr_request *request, const struct sockaddr *src, knot_pkt_t *packet);
 
 /**
@@ -159,6 +161,7 @@ int kr_resolve_consume(struct kr_request *request, const struct sockaddr *src, k
  * @param  packet  [out] packet to be filled with additional query
  * @return         any state
  */
+KR_EXPORT
 int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *type, knot_pkt_t *packet);
 
 /**
@@ -171,6 +174,7 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t
  * @param  state   either DONE or FAIL state
  * @return         DONE
  */
+KR_EXPORT
 int kr_resolve_finish(struct kr_request *request, int state);
 
 /**
@@ -178,6 +182,7 @@ int kr_resolve_finish(struct kr_request *request, int state);
  * @param  request request state
  * @return         pointer to rplan
  */
+KR_EXPORT KR_PURE
 struct kr_rplan *kr_resolve_plan(struct kr_request *request);
 
 /**
@@ -185,5 +190,6 @@ struct kr_rplan *kr_resolve_plan(struct kr_request *request);
  * @param  request request state
  * @return         mempool
  */
+KR_EXPORT KR_PURE
 mm_ctx_t *kr_resolve_pool(struct kr_request *request);
 
diff --git a/lib/rplan.c b/lib/rplan.c
index db6bad1646a7e69018d2debe865fe2e53789c1dc..8bae41cd1dab5e67bfab02c9b43c5a30db12ffbd 100644
--- a/lib/rplan.c
+++ b/lib/rplan.c
@@ -36,6 +36,11 @@ const lookup_table_t query_flag_names[] = {
 	{ 0, NULL }
 };
 
+const lookup_table_t *kr_query_flag_names(void)
+{
+	return query_flag_names;
+}
+
 static struct kr_query *query_create(mm_ctx_t *pool, const knot_dname_t *name)
 {
 	if (name == NULL) {
diff --git a/lib/rplan.h b/lib/rplan.h
index 86fd061dbeed5791a5827ea965e5a24fde706f81..0d05269c755d4c7e1140675489af4a1a7ddd866a 100644
--- a/lib/rplan.h
+++ b/lib/rplan.h
@@ -54,7 +54,8 @@ enum kr_query_flag {
 };
 
 /** Query flag names table */
-extern const lookup_table_t query_flag_names[];
+KR_EXPORT KR_CONST
+const lookup_table_t *kr_query_flag_names(void);
 
 /**
  * Single query representation.
@@ -94,12 +95,14 @@ struct kr_rplan {
  * @param request resolution request
  * @param pool ephemeral memory pool for whole resolution
  */
+KR_EXPORT
 int kr_rplan_init(struct kr_rplan *rplan, struct kr_request *request, mm_ctx_t *pool);
 
 /**
  * Deinitialize resolution plan, aborting any uncommited transactions.
  * @param rplan plan instance
  */
+KR_EXPORT
 void kr_rplan_deinit(struct kr_rplan *rplan);
 
 /**
@@ -107,6 +110,7 @@ void kr_rplan_deinit(struct kr_rplan *rplan);
  * @param rplan plan instance
  * @return true or false
  */
+KR_EXPORT KR_PURE
 bool kr_rplan_empty(struct kr_rplan *rplan);
 
 /**
@@ -119,6 +123,7 @@ bool kr_rplan_empty(struct kr_rplan *rplan);
  * @param type resolved type
  * @return query instance or NULL
  */
+KR_EXPORT
 struct kr_query *kr_rplan_push(struct kr_rplan *rplan, struct kr_query *parent,
                                const knot_dname_t *name, uint16_t cls, uint16_t type);
 
@@ -129,15 +134,19 @@ struct kr_query *kr_rplan_push(struct kr_rplan *rplan, struct kr_query *parent,
  * @param qry resolved query
  * @return 0 or an error
  */
+KR_EXPORT
 int kr_rplan_pop(struct kr_rplan *rplan, struct kr_query *qry);
 
 /**
  * Return true if resolution chain satisfies given query.
  */
+KR_EXPORT KR_PURE
 bool kr_rplan_satisfies(struct kr_query *closure, const knot_dname_t *name, uint16_t cls, uint16_t type);
 
 /** Return last resolved query. */
+KR_EXPORT KR_PURE
 struct kr_query *kr_rplan_resolved(struct kr_rplan *rplan);
 
 /** Return query predecessor. */
+KR_EXPORT KR_PURE
 struct kr_query *kr_rplan_next(struct kr_query *qry);
\ No newline at end of file
diff --git a/lib/utils.c b/lib/utils.c
index 75cd81a080613a2a79df45115936da4dc991ff55..b2511754411bc79cd4cee7136a2355624392c05c 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -17,15 +17,15 @@
 #include <stdarg.h>
 #include <string.h>
 #include <stdlib.h>
-#include <unistd.h>
 #include <stdio.h>
 #include <arpa/inet.h>
 #include <sys/time.h>
+#include <contrib/cleanup.h>
+#include <ccan/isaac/isaac.h>
 #include <libknot/descriptor.h>
 #include <libknot/dname.h>
 #include <libknot/rrtype/rrsig.h>
 
-#include "ccan/isaac/isaac.h"
 #include "lib/defines.h"
 #include "lib/utils.h"
 #include "lib/generic/array.h"
@@ -34,7 +34,7 @@
 #include "lib/resolve.h"
 
 /* Logging & debugging */
-bool _env_debug = false;
+static bool _env_debug = false;
 
 /** @internal CSPRNG context */
 static isaac_ctx ISAAC;
@@ -64,19 +64,26 @@ static inline int u16tostr(uint8_t *dst, uint16_t num)
 /*
  * Cleanup callbacks.
  */
-void _cleanup_free(char **p)
+
+bool kr_debug_set(bool status)
 {
-	free(*p);
+	return _env_debug = status;
 }
 
-void _cleanup_close(int *p)
+bool kr_debug_status(void)
 {
-	if (*p > 0) close(*p);
+	return _env_debug;
 }
 
-void _cleanup_fclose(FILE **p)
+void kr_log_debug(const char *fmt, ...)
 {
-	if (*p) fclose(*p);
+	if (_env_debug) {
+		va_list args;
+		va_start(args, fmt);
+		vprintf(fmt, args);
+		va_end(args);
+		fflush(stdout);
+	}
 }
 
 char* kr_strcatdup(unsigned n, ...)
@@ -163,7 +170,7 @@ unsigned kr_rand_uint(unsigned max)
 	return isaac_next_uint(&ISAAC, max);
 }
 
-int mm_reserve(void *baton, char **mem, size_t elm_size, size_t want, size_t *have)
+int kr_memreserve(void *baton, char **mem, size_t elm_size, size_t want, size_t *have)
 {
     if (*have >= want) {
         return 0;
@@ -205,8 +212,10 @@ int kr_pkt_put(knot_pkt_t *pkt, const knot_dname_t *name, uint32_t ttl,
 	/* Create empty RR */
 	knot_rrset_t rr;
 	knot_rrset_init(&rr, knot_dname_copy(name, &pkt->mm), rtype, rclass);
-	/* Create RDATA */
-	knot_rdata_t rdata_arr[knot_rdata_array_size(rdlen)];
+	/* Create RDATA
+	 * @warning _NOT_ thread safe.
+	 */
+	static knot_rdata_t rdata_arr[RDATA_ARR_MAX];
 	knot_rdata_init(rdata_arr, rdlen, rdata, ttl);
 	knot_rdataset_add(&rr.rrs, rdata_arr, &pkt->mm);
 	/* Append RR */
@@ -304,7 +313,7 @@ int kr_bitcmp(const char *a, const char *b, int bits)
 	return ret;
 }
 
-int kr_rrmap_key(char *key, const knot_dname_t *owner, uint16_t type, uint8_t rank)
+int kr_rrkey(char *key, const knot_dname_t *owner, uint16_t type, uint8_t rank)
 {
 	if (!key || !owner) {
 		return kr_error(EINVAL);
@@ -330,7 +339,7 @@ int kr_rrmap_add(map_t *stash, const knot_rrset_t *rr, uint8_t rank, mm_ctx_t *p
 	}
 
 	/* Stash key = {[1] flags, [1-255] owner, [5] type, [1] \x00 } */
-	char key[RRMAP_KEYSIZE];
+	char key[KR_RRKEY_LEN];
 	uint8_t extra_flags = 0;
 	uint16_t rrtype = rr->type;
 	/* Stash RRSIGs in a special cache, flag them and set type to its covering RR.
@@ -339,7 +348,7 @@ int kr_rrmap_add(map_t *stash, const knot_rrset_t *rr, uint8_t rank, mm_ctx_t *p
 		rrtype = knot_rrsig_type_covered(&rr->rrs, 0);
 		extra_flags |= KEY_FLAG_RRSIG;
 	}
-	int ret = kr_rrmap_key(key, rr->owner, rrtype, rank);
+	int ret = kr_rrkey(key, rr->owner, rrtype, rank);
 	if (ret <= 0) {
 		return kr_error(EILSEQ);
 	}
@@ -360,7 +369,7 @@ int kr_rrmap_add(map_t *stash, const knot_rrset_t *rr, uint8_t rank, mm_ctx_t *p
 
 int kr_rrarray_add(rr_array_t *array, const knot_rrset_t *rr, mm_ctx_t *pool)
 {
-	int ret = array_reserve_mm(*array, array->len + 1, mm_reserve, pool);
+	int ret = array_reserve_mm(*array, array->len + 1, kr_memreserve, pool);
 	if (ret != 0) {
 		return kr_error(ENOMEM);
 	}
diff --git a/lib/utils.h b/lib/utils.h
index cfa789cedc6a44b01bf8bd4fee38113909a71857..d47dce3b6cec0814b9baf1610ce44abf7cd0ffa0 100644
--- a/lib/utils.h
+++ b/lib/utils.h
@@ -23,38 +23,22 @@
 #include "lib/generic/map.h"
 #include "lib/generic/array.h"
 
-/*
- * General-purpose attributes.
- * @cond internal
- */
-#define auto_free __attribute__((cleanup(_cleanup_free)))
-extern void _cleanup_free(char **p);
-#define auto_close __attribute__((cleanup(_cleanup_close)))
-extern void _cleanup_close(int *p);
-#define auto_fclose __attribute__((cleanup(_cleanup_fclose)))
-extern void _cleanup_fclose(FILE **p);
-/* @endcond */
-
 /*
  * Logging and debugging.
  */
-#define log_info(fmt, ...) printf((fmt), ## __VA_ARGS__)
-#define log_error(fmt, ...) fprintf(stderr, (fmt), ## __VA_ARGS__)
+#define kr_log_info(fmt, ...) printf((fmt), ## __VA_ARGS__)
+#define kr_log_error(fmt, ...) fprintf(stderr, (fmt), ## __VA_ARGS__)
 #ifndef NDEBUG
-extern bool _env_debug; /* @internal cond variable */
 /* Toggle debug messages */
-#define log_debug_enable(x) _env_debug = (x)
-#define log_debug_status() _env_debug
-/* Message logging */
-#define log_debug(fmt, ...) do { \
-    if (_env_debug) { printf((fmt), ## __VA_ARGS__); fflush(stdout); } \
-    } while (0)
+KR_EXPORT bool kr_debug_set(bool status);
+KR_EXPORT KR_PURE bool kr_debug_status(void);
+KR_EXPORT void kr_log_debug(const char *fmt, ...);
 /* Debug block */
-#define WITH_DEBUG if(__builtin_expect(_env_debug, 0))
+#define WITH_DEBUG if(__builtin_expect(kr_debug_status(), 0))
 #else
-#define log_debug_status() false
-#define log_debug_enable(x)
-#define log_debug(fmt, ...)
+#define kr_debug_status() false
+#define kr_debug_set(x)
+#define kr_log_debug(fmt, ...)
 #define WITH_DEBUG if(0)
 #endif
 
@@ -77,51 +61,71 @@ struct kr_context;
 typedef array_t(knot_rrset_t *) rr_array_t;
 /* @endcond */
 
+/** @internal RDATA array maximum size. */
+#define RDATA_ARR_MAX (UINT16_MAX + sizeof(uint64_t))
 /** @internal Next RDATA shortcut. */
 #define kr_rdataset_next(rd) (rd + knot_rdata_array_size(knot_rdata_rdlen(rd)))
 
 /** Concatenate N strings. */
+KR_EXPORT
 char* kr_strcatdup(unsigned n, ...);
 
 /** Reseed CSPRNG context. */
 int kr_rand_reseed(void);
 
 /** Get pseudo-random value. */
+KR_EXPORT
 unsigned kr_rand_uint(unsigned max);
 
 /** Memory reservation routine for mm_ctx_t */
-int mm_reserve(void *baton, char **mem, size_t elm_size, size_t want, size_t *have);
+KR_EXPORT
+int kr_memreserve(void *baton, char **mem, size_t elm_size, size_t want, size_t *have);
 
 /** @internal Fast packet reset. */
+KR_EXPORT
 int kr_pkt_recycle(knot_pkt_t *pkt);
 
 /** Construct and put record to packet. */
+KR_EXPORT
 int kr_pkt_put(knot_pkt_t *pkt, const knot_dname_t *name, uint32_t ttl,
                uint16_t rclass, uint16_t rtype, const uint8_t *rdata, uint16_t rdlen);
 
 /** Address bytes for given family. */
+KR_EXPORT KR_PURE
 const char *kr_inaddr(const struct sockaddr *addr);
 /** Address length for given family. */
+KR_EXPORT KR_PURE
 int kr_inaddr_len(const struct sockaddr *addr);
 /** Return address type for string. */
+KR_EXPORT KR_PURE
 int kr_straddr_family(const char *addr);
 /** Return address length in given family. */
+KR_EXPORT KR_CONST
 int kr_family_len(int family);
 /** Parse address and return subnet length (bits).
   * @warning 'dst' must be at least `sizeof(struct in6_addr)` long. */
+KR_EXPORT
 int kr_straddr_subnet(void *dst, const char *addr);
 /** Compare memory bitwise. */
+KR_EXPORT KR_PURE
 int kr_bitcmp(const char *a, const char *b, int bits);
 
 /** @internal RR map flags. */
 #define KEY_FLAG_RRSIG 0x02
 #define KEY_FLAG_RANK(key) (key[0] >> 2)
 #define KEY_COVERING_RRSIG(key) (key[0] & KEY_FLAG_RRSIG)
-/* Stash key = {[1] flags, [1-255] owner, [5] type, [1] \x00 } */
-#define RRMAP_KEYSIZE (9 + KNOT_DNAME_MAXLEN)
 
-/** @internal Create unique string key for RR. */
-int kr_rrmap_key(char *key, const knot_dname_t *owner, uint16_t type, uint8_t rank);
+/* Stash key = {[1] flags, [1-255] owner, [5] type, [1] \x00 } */
+#define KR_RRKEY_LEN (9 + KNOT_DNAME_MAXLEN)
+/** Create unique null-terminated string key for RR.
+  * @param key Destination buffer for key size, MUST be KR_RRKEY_LEN or larger.
+  * @param owner RR owner domain name.
+  * @param type RR type.
+  * @param rank RR rank (8 bit tag usable for anything).
+  * @return key length if successful or an error
+  * */
+KR_EXPORT
+int kr_rrkey(char *key, const knot_dname_t *owner, uint16_t type, uint8_t rank);
 
 /** @internal Merges RRSets with matching owner name and type together.
  * @note RRSIG RRSets are merged according the type covered fields.
@@ -135,4 +139,5 @@ int kr_rrarray_add(rr_array_t *array, const knot_rrset_t *rr, mm_ctx_t *pool);
 /**
  * Call module property.
  */
+KR_EXPORT
 char *kr_module_call(struct kr_context *ctx, const char *module, const char *prop, const char *input);
diff --git a/lib/zonecut.c b/lib/zonecut.c
index ca120b7e52ac741b1b5e16481de115e7aab40a2f..05e18a9cf8eab42977541409152c211bb3fd3990 100644
--- a/lib/zonecut.c
+++ b/lib/zonecut.c
@@ -223,7 +223,7 @@ int kr_zonecut_add(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rd
 		return kr_ok();
 	}
 	/* Push new address */
-	int ret = pack_reserve_mm(*pack, 1, rdlen, mm_reserve, cut->pool);
+	int ret = pack_reserve_mm(*pack, 1, rdlen, kr_memreserve, cut->pool);
 	if (ret != 0) {
 		return kr_error(ENOMEM);
 	}
@@ -271,6 +271,8 @@ int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut)
 	if (!ctx || !cut) {
 		return kr_error(EINVAL);
 	}
+	/* @warning _NOT_ thread-safe */
+	static knot_rdata_t rdata_arr[RDATA_ARR_MAX];
 
 	update_cut_name(cut, U8(""));
 	map_walk(&cut->nsset, free_addr_set, cut->pool);
@@ -284,9 +286,8 @@ int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut)
 		/* Copy compiled-in root hints */
 		for (unsigned i = 0; i < HINT_COUNT; ++i) {
 			const struct hint_info *hint = &SBELT[i];
-			knot_rdata_t rdata[knot_rdata_array_size(hint->len)];
-			knot_rdata_init(rdata, hint->len, hint->addr, 0);
-			ret = kr_zonecut_add(cut, hint->name, rdata);
+			knot_rdata_init(rdata_arr, hint->len, hint->addr, 0);
+			ret = kr_zonecut_add(cut, hint->name, rdata_arr);
 			if (ret != 0) {
 				break;
 			}
diff --git a/lib/zonecut.h b/lib/zonecut.h
index a3eca008225a287cc1a4e46fae13ba8852e979f0..64ebd7b5b998b325171ef1faa2f416138ed0e570 100644
--- a/lib/zonecut.h
+++ b/lib/zonecut.h
@@ -18,6 +18,7 @@
 
 #include "lib/generic/map.h"
 #include "lib/generic/pack.h"
+#include "lib/defines.h"
 #include "lib/cache.h"
 
 struct kr_rplan;
@@ -42,12 +43,14 @@ struct kr_zonecut {
  * @param pool
  * @return 0 or error code
  */
+KR_EXPORT
 int kr_zonecut_init(struct kr_zonecut *cut, const knot_dname_t *name, mm_ctx_t *pool);
 
 /**
  * Clear the structure and free the address set.
  * @param cut zone cut
  */
+KR_EXPORT
 void kr_zonecut_deinit(struct kr_zonecut *cut);
 
 /**
@@ -56,6 +59,7 @@ void kr_zonecut_deinit(struct kr_zonecut *cut);
  * @param cut  zone cut to be set
  * @param name new zone cut name
  */
+KR_EXPORT
 void kr_zonecut_set(struct kr_zonecut *cut, const knot_dname_t *name);
 
 /** 
@@ -64,6 +68,7 @@ void kr_zonecut_set(struct kr_zonecut *cut, const knot_dname_t *name);
  * @param src source zone cut
  * @return 0 or an error code
  */
+KR_EXPORT
 int kr_zonecut_copy(struct kr_zonecut *dst, const struct kr_zonecut *src);
 
 /**
@@ -72,6 +77,7 @@ int kr_zonecut_copy(struct kr_zonecut *dst, const struct kr_zonecut *src);
  * @param src source zone cut
  * @return 0 or an error code
  */
+KR_EXPORT
 int kr_zonecut_copy_trust(struct kr_zonecut *dst, const struct kr_zonecut *src);
 
 /**
@@ -85,6 +91,7 @@ int kr_zonecut_copy_trust(struct kr_zonecut *dst, const struct kr_zonecut *src);
  * @param rdata  nameserver address (as rdata)
  * @return 0 or error code
  */
+KR_EXPORT
 int kr_zonecut_add(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rdata_t *rdata);
 
 /**
@@ -94,6 +101,7 @@ int kr_zonecut_add(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rd
  * @param  rdata name server address
  * @return       0 or error code
  */
+KR_EXPORT
 int kr_zonecut_del(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rdata_t *rdata);
 
 /**
@@ -106,6 +114,7 @@ int kr_zonecut_del(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rd
  * @param  ns    name server name
  * @return       pack of addresses or NULL
  */
+KR_EXPORT KR_PURE
 pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns);
 
 /**
@@ -115,6 +124,7 @@ pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns);
  * @param cut zone cut to be populated
  * @return 0 or error code
  */
+KR_EXPORT
 int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut);
 
 /**
@@ -128,5 +138,6 @@ int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut);
  * @param secured   set to true if want secured zone cut, will return false if it is provably insecure
  * @return 0 or error code (ENOENT if it doesn't find anything)
  */
+KR_EXPORT
 int kr_zonecut_find_cached(struct kr_context *ctx, struct kr_zonecut *cut, const knot_dname_t *name,
                            struct kr_cache_txn *txn, uint32_t timestamp, bool * restrict secured);
diff --git a/modules/cachectl/cachectl.mk b/modules/cachectl/cachectl.mk
index 6707dbdca13ee5258270c1956ac8d7cdd63f6b88..f39cfa60a3c06df82872424c01e515cc7d8c0674 100644
--- a/modules/cachectl/cachectl.mk
+++ b/modules/cachectl/cachectl.mk
@@ -1,5 +1,5 @@
-cachectl_CFLAGS := -fvisibility=hidden
+cachectl_CFLAGS := -fvisibility=hidden -fPIC
 cachectl_SOURCES := modules/cachectl/cachectl.c
 cachectl_DEPEND := $(libkres)
-cachectl_LIBS := $(libkres_TARGET) $(libkres_LIBS)
+cachectl_LIBS := $(contrib_TARGET) $(libkres_TARGET) $(libkres_LIBS)
 $(call make_c_module,cachectl)
\ No newline at end of file
diff --git a/modules/hints/hints.c b/modules/hints/hints.c
index 99667e9bfd2dbd91498c25fc9fcfb99624082ff3..03c13ce0699e6bea705f9731d815ad9bab4866bb 100644
--- a/modules/hints/hints.c
+++ b/modules/hints/hints.c
@@ -26,6 +26,7 @@
 #include <libknot/rrtype/aaaa.h>
 #include <ccan/json/json.h>
 #include <ucw/mempool.h>
+#include <contrib/cleanup.h>
 
 #include "daemon/engine.h"
 #include "lib/zonecut.h"
@@ -230,13 +231,13 @@ static int add_pair(struct kr_zonecut *hints, const char *name, const char *addr
 		return kr_error(EINVAL);
 	}
 
-	/* Build rdata */
-	size_t addr_len = 0;
-	uint8_t *raw_addr = sockaddr_raw(&ss, &addr_len);
-	knot_rdata_t rdata[knot_rdata_array_size(addr_len)];
-	knot_rdata_init(rdata, addr_len, raw_addr, 0);
-
-	return kr_zonecut_add(hints, key, rdata);
+	/* Build RDATA */
+	size_t addr_len = kr_inaddr_len((struct sockaddr *)&ss);
+	const uint8_t *raw_addr = (const uint8_t *)kr_inaddr((struct sockaddr *)&ss);
+	/* @warning _NOT_ thread-safe */
+	static knot_rdata_t rdata_arr[RDATA_ARR_MAX];
+	knot_rdata_init(rdata_arr, addr_len, raw_addr, 0);
+	return kr_zonecut_add(hints, key, rdata_arr);
 }
 
 static int load_map(struct kr_zonecut *hints, FILE *fp)
diff --git a/modules/hints/hints.mk b/modules/hints/hints.mk
index c726391189068189237c857bc720e533971f73af..c7bb521be5536c35e76ab7e3ea3e43966dd405e4 100644
--- a/modules/hints/hints.mk
+++ b/modules/hints/hints.mk
@@ -1,5 +1,5 @@
-hints_CFLAGS := -fvisibility=hidden
+hints_CFLAGS := -fvisibility=hidden -fPIC
 hints_SOURCES := modules/hints/hints.c
 hints_DEPEND := $(libkres)
-hints_LIBS := $(libkres_TARGET) $(libkres_LIBS)
+hints_LIBS := $(contrib_TARGET) $(libkres_TARGET) $(libkres_LIBS)
 $(call make_c_module,hints)
\ No newline at end of file
diff --git a/modules/kmemcached/kmemcached.mk b/modules/kmemcached/kmemcached.mk
index 87462a707135922d59cb6220a16f276ee09ba85e..148bd5952918d8bf227f1f510cb0860941143673 100644
--- a/modules/kmemcached/kmemcached.mk
+++ b/modules/kmemcached/kmemcached.mk
@@ -1,4 +1,4 @@
-kmemcached_CFLAGS := -fvisibility=hidden
+kmemcached_CFLAGS := -fvisibility=hidden -fPIC
 kmemcached_SOURCES := modules/kmemcached/kmemcached.c modules/kmemcached/namedb_memcached.c
 kmemcached_LIBS := $(libkres_TARGET) $(libkres_LIBS) $(libmemcached_LIBS)
 $(call make_c_module,kmemcached)
diff --git a/modules/redis/redis.mk b/modules/redis/redis.mk
index 765f231aeef5060980edbad8ac45d3c47ca54b8a..2cb4463a7e0fd2a2cdcf7e85ca62945056858133 100644
--- a/modules/redis/redis.mk
+++ b/modules/redis/redis.mk
@@ -1,4 +1,4 @@
-redis_CFLAGS := -fvisibility=hidden
+redis_CFLAGS := -fvisibility=hidden -fPIC
 redis_SOURCES := modules/redis/redis.c modules/redis/namedb_redis.c
 redis_LIBS := $(libkres_TARGET) $(libkres_LIBS) $(hiredis_LIBS) $(libuv_LIBS)
 $(call make_c_module,redis)
diff --git a/modules/stats/stats.c b/modules/stats/stats.c
index 11c1c8a33edccb3f52cfc12edb78c0564c9b0911..2b3e6219e7a4f25747a18cddbd3c0793a6528184 100644
--- a/modules/stats/stats.c
+++ b/modules/stats/stats.c
@@ -24,6 +24,7 @@
 
 #include <libknot/packet/pkt.h>
 #include <ccan/json/json.h>
+#include <contrib/cleanup.h>
 
 #include "lib/layer/iterate.h"
 #include "lib/rplan.h"
diff --git a/modules/stats/stats.mk b/modules/stats/stats.mk
index ef8a55e8304701625f5fed1a9205c9959f04eaf3..17e462472787a02baaaf19007af1b8f7e647b213 100644
--- a/modules/stats/stats.mk
+++ b/modules/stats/stats.mk
@@ -1,5 +1,5 @@
-stats_CFLAGS := -fvisibility=hidden
+stats_CFLAGS := -fvisibility=hidden -fPIC
 stats_SOURCES := modules/stats/stats.c
-stats_DEPEND := $(libkres)
-stats_LIBS := $(libkres_TARGET) $(libkres_LIBS)
+stats_DEPEND := $(libkres) $(contrib)
+stats_LIBS := $(contrib_TARGET) $(libkres_TARGET) $(libkres_LIBS)
 $(call make_c_module,stats)
diff --git a/platform.mk b/platform.mk
index 182755389ea68cd0dc7ad4a0bbdfbf237690e2f1..dd01c96841f3469dfc9ec549758f52e1ee86ce1c 100644
--- a/platform.mk
+++ b/platform.mk
@@ -2,6 +2,7 @@
 # Don't touch this unless you're changing the way targets are compiled
 # You have been warned
 
+# Platform-dependent stuff checks
 CCLD := $(CC)
 CGO := go tool cgo
 GO := go
@@ -15,6 +16,10 @@ ARTYPE  := static
 BINEXT :=
 PLATFORM = Linux
 ARCH := $(word 1, $(subst -, ,$(shell $(CC) -dumpmachine)))
+# Library versioning flags (platform-specific)
+SOVER = 
+# Library versioned extension (platform-specific)
+SOVER_EXT = $(LIBEXT).$(1)
 ifeq ($(OS),Windows_NT)
 	PLATFORM := Windows
 	RM := del
@@ -27,9 +32,21 @@ else
         PLATFORM := Darwin
         LIBEXT := .dylib
         MODTYPE := dynamiclib
+        # OS X specific hardening since -pie doesn't work
+        ifneq ($(HARDENING),no)
+            BINFLAGS += -Wl,-pie
+        endif
+        # Version is prepended to dylib
+        SOVER_EXT = .$(1)$(LIBEXT)
+        SOVER = $(if $(1), -compatibility_version $(2) -current_version $(1),)
     else
         PLATFORM := POSIX
         LDFLAGS += -pthread -lm -Wl,-E
+        # ELF hardening options
+        ifneq ($(HARDENING),no)
+            BINFLAGS += -pie
+            LDFLAGS += -Wl,-z,relro,-z,now
+        endif
         ifeq (,$(findstring BSD,$(UNAME)))
             LDFLAGS += -ldl
         endif
@@ -41,16 +58,12 @@ ifeq ($(V),1)
 	quiet = $($1)
 else
 	quiet = @echo "  $1	$2"; $($1)
-endif	
-
-%.o: %.c
-	$(call quiet,CC,$<) $(BUILD_CFLAGS) -MMD -MP -c $< -o $@
+endif
 
 # Make objects and depends (name)
 define make_objs
 $(1)_OBJ := $$($(1)_SOURCES:.c=.o)
 $(1)_DEP := $$($(1)_SOURCES:.c=.d)
-
 -include $$($(1)_DEP)
 endef
 
@@ -67,23 +80,34 @@ endif
 else
 $$(eval $$(call make_objs,$(1)))
 endif
+# Rules to generate objects with custom CFLAGS and binary/library
+$$($(1)_OBJ): $$($(1)_SOURCES)
+	$(call quiet,CC,$$(@:%.o=%.c)) $(BUILD_CFLAGS) $$($(1)_CFLAGS) -MMD -MP -c $$(@:%.o=%.c) -o $$@
 $(1) := $(2)/$(1)$(3)
 $(2)/$(1)$(3): $$($(1)_OBJ) $$($(1)_DEPEND)
 ifeq ($(4),-$(ARTYPE))
 	$(call quiet,AR,$$@) rcs $$@ $$($(1)_OBJ)
 else
-	$(call quiet,CCLD,$$@) $(BUILD_CFLAGS) $$($(1)_CFLAGS) $$($(1)_OBJ) -o $$@ $(4) $$($(1)_LIBS) $(BUILD_LDFLAGS)
+	$(call quiet,CCLD,$$@) $$($(1)_CFLAGS) $(BUILD_CFLAGS) $$($(1)_OBJ) $(call SOVER,$(7),$(7)) -o $$@ $(4) $$($(1)_LIBS) $(BUILD_LDFLAGS) $$($(1)_LDFLAGS)
 endif
+# Additional rules
 $(1)-clean:
 	$(RM) $$($(1)_OBJ) $$($(1)_DEP) $(2)/$(1)$(3)
 ifeq ($(6), yes)
 	$(RM) $(1).amalg.c $(1).amalg.o
 endif
 $(1)-install: $(2)/$(1)$(3)
+# Modules install to special path
 ifneq ($(5),$(MODULEDIR))
 	$(INSTALL) -d $(DESTDIR)$(5)
 endif
+# Versioned library install
+ifneq ($(strip $(7)),)
+	$(INSTALL) $(2)/$(1)$(3) $(DESTDIR)$(5)/$(1)$(call SOVER_EXT,$(7))
+	$(LN) -f $(1)$(call SOVER_EXT,$(7)) $(DESTDIR)$(5)/$(1)$(3)
+else
 	$(INSTALL) $(2)/$(1)$(3) $(DESTDIR)$(5)
+endif
 ifneq ($$(strip $$($(1)_HEADERS)),)
 	$(INSTALL) -d $(DESTDIR)$(INCLUDEDIR)/$(1)
 	$(INSTALL) -m 644 $$($(1)_HEADERS) $(DESTDIR)$(INCLUDEDIR)/$(1)
@@ -91,9 +115,9 @@ endif
 .PHONY: $(1)-clean $(1)-install
 endef
 
-# Make targets (name,path,amalgable yes|no)
+# Make targets (name,path,amalgable yes|no,abiver)
 make_bin = $(call make_target,$(1),$(2),$(BINEXT),$(BINFLAGS),$(BINDIR),$(3))
-make_lib = $(call make_target,$(1),$(2),$(LIBEXT),-$(LIBTYPE),$(LIBDIR),$(3))
+make_lib = $(call make_target,$(1),$(2),$(LIBEXT),-$(LIBTYPE),$(LIBDIR),$(3),$(4))
 make_module = $(call make_target,$(1),$(2),$(LIBEXT),-$(LIBTYPE),$(MODULEDIR),$(3))
 make_shared = $(call make_target,$(1),$(2),$(MODEXT),-$(MODTYPE),$(LIBDIR),$(3))
 make_static = $(call make_target,$(1),$(2),$(AREXT),-$(ARTYPE),$(LIBDIR),$(3))
diff --git a/scripts/kresd.apparmor b/scripts/kresd.apparmor
new file mode 100644
index 0000000000000000000000000000000000000000..81fa5a1e3b73908c1fce9d2c51c89a739c6be4a8
--- /dev/null
+++ b/scripts/kresd.apparmor
@@ -0,0 +1,30 @@
+#include <tunables/global>
+
+/usr/bin/kresd {
+  #include <abstractions/base>
+  #include <abstractions/nameservice>
+  capability net_bind_service,
+  capability setgid,
+  capability setuid,
+  # seems to be needed during start to read /var/lib/kresd
+  # while we still run as root.
+  capability dac_override,
+
+  network tcp,
+  network udp,
+
+  /proc/sys/net/core/somaxconn r,
+  /etc/kresd/* r, 
+  /var/lib/kresd/ r,
+  /var/lib/kresd/** rwlk,
+
+  # modules
+  /usr/lib{,64}/kdns_modules/*.lua r,
+  /usr/lib{,64}/kdns_modules/*.so rm,
+
+  # for tinyweb
+  /usr/lib{,64}/kdns_modules/tinyweb/ r,
+  /usr/lib{,64}/kdns_modules/tinyweb/* r,
+  /var/lib/GeoIP/* r,
+}
+
diff --git a/scripts/kresd.service b/scripts/kresd.service
new file mode 100644
index 0000000000000000000000000000000000000000..b6a00ad5cda1c208c4bf94ec5066584c699eecaf
--- /dev/null
+++ b/scripts/kresd.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Knot DNS Resolver daemon
+After=syslog.target network.target auditd.service
+
+[Service]
+Type=simple
+EnvironmentFile=-/etc/sysconfig/kresd
+ExecStart=/usr/bin/kresd -c /etc/kresd/config -f $KRESD_WORKERS $KRESD_OPTIONS /var/lib/kresd/
+Restart=on-abort
+
+[Install]
+WantedBy=multi-user.target
diff --git a/scripts/kresd.sysconfig b/scripts/kresd.sysconfig
new file mode 100644
index 0000000000000000000000000000000000000000..ceb5e3068a44d38161979ab78e4e8ff2f26d7aa4
--- /dev/null
+++ b/scripts/kresd.sysconfig
@@ -0,0 +1,14 @@
+## Path:        System/DNS
+## Description: Number of worker processes to spawn
+## Type:        integer
+## Default:     1
+## ServiceRestart:      kresd
+##
+#
+# Number of workers to spawn for kresd.
+# If you get start up failures with "already in use" your libuv is too
+# old and you have to stick to 1.
+#
+KRESD_WORKERS=1
+# Additional options
+KRESD_OPTIONS=
diff --git a/tests/test_utils.c b/tests/test_utils.c
index f4e1b582eb1c7edfaba1b546fb6bbe3685a465aa..c874ef8d68649103235b2fd0e84534db4725acd8 100644
--- a/tests/test_utils.c
+++ b/tests/test_utils.c
@@ -16,6 +16,7 @@
 
 #include <sys/socket.h>
 #include <stdio.h>
+#include <contrib/cleanup.h>
 
 #include "tests/test.h"
 #include "lib/utils.h"
diff --git a/tests/unit.mk b/tests/unit.mk
index 98967130bee91537a5a98a8b24ea05c710fa3bc8..9e16890957a31c79f127c5e5956a439efa25f170 100644
--- a/tests/unit.mk
+++ b/tests/unit.mk
@@ -14,6 +14,7 @@ tests_BIN := \
 	test_zonecut \
 	test_rplan
 
+mock_cmodule_CFLAGS := -fPIC
 mock_cmodule_SOURCES := tests/mock_cmodule.c
 $(eval $(call make_lib,mock_cmodule,tests))
 
@@ -21,14 +22,22 @@ $(eval $(call make_lib,mock_cmodule,tests))
 tests_DEPEND := $(libkres) $(mock_cmodule) $(mock_gomodule)
 tests_LIBS :=  $(libkres_TARGET) $(libkres_LIBS) $(cmocka_LIBS)
 
+# Platform-specific library injection
+ifeq ($(PLATFORM),Darwin)
+	preload_syms := DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_LIBRARY_PATH="$(DYLD_LIBRARY_PATH):$(abspath lib)"
+else
+	preload_syms := LD_LIBRARY_PATH="$(LD_LIBRARY_PATH):$(abspath lib)"
+endif
+
 # Make test binaries
 define make_test
+$(1)_CFLAGS := -fPIE
 $(1)_SOURCES := tests/$(1).c
 $(1)_LIBS := $(tests_LIBS)
 $(1)_DEPEND := $(tests_DEPEND)
 $(call make_bin,$(1),tests)
 $(1): $$($(1))
-	@$$<
+	@$(preload_syms) $$<
 .PHONY: $(1)
 endef