diff --git a/daemon/bindings.c b/daemon/bindings.c
index 29af9e62d3d18b0119e40436cacd4e5ab33c5010..bef846f63351e690181996d2d650aa3a71d5f2fe 100644
--- a/daemon/bindings.c
+++ b/daemon/bindings.c
@@ -449,6 +449,7 @@ static void event_callback(uv_timer_t *timer)
 	}
 	/* Clear the stack, there may be event a/o enything returned */
 	lua_settop(L, top);
+	lua_gc(L, LUA_GCCOLLECT, 0);
 	/* Free callback if not recurrent or an error */
 	if (ret != 0 || uv_timer_get_repeat(timer) == 0) {
 		uv_close((uv_handle_t *)timer, (uv_close_cb) event_free);
diff --git a/daemon/bindings/kres.c b/daemon/bindings/kres.c
deleted file mode 100644
index c20e249df680af432cda18e2cd2c3febfb149c0e..0000000000000000000000000000000000000000
--- a/daemon/bindings/kres.c
+++ /dev/null
@@ -1,400 +0,0 @@
-/*  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/>.
- */
-
-#include <libknot/descriptor.h>
-#include "daemon/bindings/kres.h"
-#include "daemon/bindings.h"
-
-#define WRAP_NUMBER(L, name, val) \
-	lua_pushnumber((L), (val)); \
-	lua_setfield((L), -2, (name))
-
-#define WRAP_CONST(L, name, prefix...) \
-	WRAP_NUMBER(L, #name, prefix ## name)
-
-#define WRAP_LUT(L, prefix, table) \
-	lua_newtable(L); \
-	for (const lookup_table_t *elm = (table); elm->name; ++elm) { \
-		WRAP_NUMBER((L), elm->name, elm->id); \
-	} \
-	lua_setfield((L), -2, (prefix))
-
-#define LUA_ERRSTR(L, errstr) \
-	lua_pushliteral(L, errstr); \
-	lua_error(L)
-
-#define CHECK_UDATA(udata, L) \
-	lua_touserdata(L, 1); if (!udata) return 0
-
-/** @internal Register metatable. */
-static void lua_register_meta(lua_State *L, const luaL_Reg *funcs, const char *name)
-{
-	luaL_newmetatable(L, name); \
-	luaL_setfuncs(L, funcs, 0); \
-	lua_pushvalue(L, -1); \
-	lua_setfield(L, -2, "__index"); \
-	lua_pop(L, 1);
-}
-
-/** @internal Shortcut for dname conversion. */
-static inline void lua_pushdname(lua_State *L, const knot_dname_t *name)
-{
-	char dname_str[KNOT_DNAME_MAXLEN];
-	knot_dname_to_str(dname_str, name, sizeof(dname_str));
-	lua_pushstring(L, dname_str);
-}
-
-/*
- * Record types, since the libknot doesn't export them.
- */
-#define RECORD_TYPES(X) \
-	X(A) X(NS) X(CNAME) X(SOA) X(PTR) X(HINFO) X(MINFO) X(MX) \
-	X(TXT) X(RP) X(AFSDB) X(RT) X(SIG) X(KEY) X(AAAA) X(LOC) \
-	X(SRV) X(NAPTR) X(KX) X(CERT) X(DNAME) X(OPT) X(APL) X(DS) \
-	X(SSHFP) X(IPSECKEY) X(RRSIG) X(NSEC) X(DNSKEY) X(DHCID) \
-	X(NSEC3) X(NSEC3PARAM) X(TLSA) X(CDS) X(CDNSKEY) X(SPF) \
-	X(NID) X(L32) X(L64) X(LP) X(EUI48) X(EUI64) X(TKEY) \
-	X(TSIG) X(IXFR) X(AXFR) X(ANY)
-
-static lookup_table_t rrtype_names[] = {
-	#define X(rc) { KNOT_RRTYPE_ ## rc, #rc },
-	RECORD_TYPES(X)
-	#undef X
-	{ 0, NULL }
-};
-
-/*
- * Record class names.
- */
-#define RECORD_CLASS(X) X(IN) X(CH) X(NONE) X(ANY)
-static lookup_table_t rrclass_names[] = {
-	#define X(rc) { KNOT_CLASS_ ## rc, #rc },
-	RECORD_CLASS(X)
-	#undef X
-	{ 0, NULL }
-};
-
-/* 
- * Packet interface
- * @note Packets are always light userdata, use single pointers.
- */
-
-#define WIRE_FLAGS(X) \
-	X(AA,aa) X(AD,ad) X(CD,cd) X(RD,rd) X(QR,qr) X(RA,ra) X(TC,tc)
-enum {
-	#define X(flag, _) WIRE_ ## flag,
-	WIRE_FLAGS(X)
-	#undef X
-};
-static lookup_table_t wire_flag_names[] = {
-	#define X(flag, _) { WIRE_ ## flag, #flag },
-	WIRE_FLAGS(X)
-	#undef X
-	{ 0, NULL }
-};
-
-#define PKT_UDATA_CHECK(L) \
-	if (!lua_touserdata(L, 1)) { \
-		LUA_ERRSTR(L, "bad parameters, expected (pkt[, newvalue])"); \
-	}
-
-static int pkt_flag(lua_State *L)
-{
-	knot_pkt_t *pkt = CHECK_UDATA(pkt, L);
-	if (lua_gettop(L) > 1 && lua_isnumber(L, 2)) {
-		int flag_id = lua_tonumber(L, 2);
-		switch(flag_id) {
-		#define X(flag, code) case WIRE_ ## flag: knot_wire_set_ ## code (pkt->wire); break;
-		WIRE_FLAGS(X)
-		#undef X
-		}
-	}
-	return 0;
-}
-
-static int pkt_opcode(lua_State *L)
-{
-	knot_pkt_t *pkt = CHECK_UDATA(pkt, L);
-	if (lua_gettop(L) > 1 && lua_isnumber(L, 2)) {
-		knot_wire_set_opcode(pkt->wire, lua_tonumber(L, 2));
-	}
-	lua_pushnumber(L, knot_wire_get_opcode(pkt->wire));
-	return 1;
-}
-
-static int pkt_rcode(lua_State *L)
-{
-	knot_pkt_t *pkt = CHECK_UDATA(pkt, L);
-	if (lua_gettop(L) > 1 && lua_isnumber(L, 2)) {
-		knot_wire_set_rcode(pkt->wire, lua_tonumber(L, 2));
-	}
-	lua_pushnumber(L, knot_wire_get_rcode(pkt->wire));
-	return 1;
-}
-
-static int pkt_qtype(lua_State *L)
-{
-	knot_pkt_t *pkt = CHECK_UDATA(pkt, L);
-	lua_pushnumber(L, knot_pkt_qtype(pkt));
-	return 1;
-}
-
-static int pkt_qclass(lua_State *L)
-{
-	knot_pkt_t *pkt = CHECK_UDATA(pkt, L);
-	lua_pushnumber(L, knot_pkt_qclass(pkt));
-	return 1;
-}
-
-static int pkt_qname(lua_State *L)
-{
-	knot_pkt_t *pkt = CHECK_UDATA(pkt, L);
-	lua_pushdname(L, knot_pkt_qname(pkt));
-	return 1;
-}
-
-static int pkt_question(lua_State *L)
-{
-	knot_pkt_t *pkt = CHECK_UDATA(pkt, L);
-	if (lua_gettop(L) < 3) {
-		return 0;
-	}
-	/* Check parameters */
-	uint8_t dname[KNOT_DNAME_MAXLEN];
-	knot_dname_from_str(dname, lua_tostring(L, 2), sizeof(dname));
-	uint16_t rrtype = lua_tointeger(L, 3);
-	uint16_t rrclass = lua_tointeger(L, 4);
-	if (!lua_isnumber(L, 3)) {
-		LUA_ERRSTR(L, "invalid RR type");
-	}
-	if (!lua_isnumber(L, 4)) { /* Default class is IN */
-		rrclass = KNOT_CLASS_IN;
-	}
-	if (!knot_dname_is_equal(knot_pkt_qname(pkt), dname) || pkt->rrset_count > 0) {
-		KR_PKT_RECYCLE(pkt);
-		knot_pkt_put_question(pkt, dname, rrclass, rrtype);
-		pkt->parsed = pkt->size;
-	}
-	return 0;
-}
-
-static int pkt_begin(lua_State *L)
-{
-	knot_pkt_t *pkt = CHECK_UDATA(pkt, L);
-	if (!lua_isnumber(L, 2) || lua_tonumber(L, 2) < pkt->current) {
-		LUA_ERRSTR(L, "bad parameters, expected packet section >= current");
-	}
-	knot_pkt_begin(pkt, lua_tointeger(L, 2));
-	return 0;
-}
-
-static int pkt_add(lua_State *L)
-{
-	knot_pkt_t *pkt = CHECK_UDATA(pkt, L);
-	if (lua_gettop(L) < 6) {
-		return 0;
-	}
-	/* Get parameters */
-	uint8_t dname[KNOT_DNAME_MAXLEN];
-	knot_dname_from_str(dname, lua_tostring(L, 2), sizeof(dname));
-	uint16_t rrclass = lua_tointeger(L, 3);
-	uint16_t rrtype = lua_tointeger(L, 4);
-	uint32_t ttl = lua_tointeger(L, 5);
-	size_t rdlen = 0;
-	const char *raw_data = lua_tolstring(L, 6, &rdlen);
-	/* Create empty RR */
-	knot_rrset_t rr;
-	knot_rrset_init(&rr, knot_dname_copy(dname, &pkt->mm), rrtype, rrclass);
-	/* Create RDATA */
-	knot_rdata_t rdata[knot_rdata_array_size(rdlen)];
-	knot_rdata_init(rdata, rdlen, (const uint8_t *)raw_data, ttl);
-	knot_rdataset_add(&rr.rrs, rdata, &pkt->mm);
-	/* Append RR */
-	int ret = knot_pkt_put(pkt, 0, &rr, KNOT_PF_FREE);
-	lua_pushboolean(L, ret == 0);
-	pkt->parsed = pkt->size;
-	return 1;
-}
-
-static int pkt_get(lua_State *L)
-{
-	knot_pkt_t *pkt = CHECK_UDATA(pkt, L);
-	if (lua_gettop(L) < 3) {
-		return 0;
-	}
-	/* Get parameters */
-	uint16_t section_id = lua_tointeger(L, 2);
-	uint16_t index = lua_tointeger(L, 3);
-	/* Get RR */
-	const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id);
-	if (!sec || sec->count <= index) {
-		return 0;
-	}
-	const knot_rrset_t *rr = knot_pkt_rr(sec, index);
-	lua_newtable(L);
-	lua_pushdname(L, rr->owner);
-	lua_setfield(L, -2, "owner");
-	lua_pushnumber(L, rr->rclass);
-	lua_setfield(L, -2, "class");
-	lua_pushnumber(L, rr->type);
-	lua_setfield(L, -2, "type");
-	lua_pushnumber(L, knot_rrset_ttl(rr));
-	lua_setfield(L, -2, "ttl");
-	lua_pushlightuserdata(L, (void *)&rr->rrs);
-	lua_setfield(L, -2, "rdata");
-	return 1;
-}
-
-static int pkt_meta_register(lua_State *L)
-{
-	static const luaL_Reg wrap[] = {
-		{ "flag",      pkt_flag   },
-		{ "rcode",     pkt_rcode  },
-		{ "opcode",    pkt_opcode },
-		{ "qtype",     pkt_qtype  },
-		{ "qclass",    pkt_qclass },
-		{ "qname",     pkt_qname  },
-		{ "question",  pkt_question },
-		{ "begin",     pkt_begin },
-		{ "add",       pkt_add },
-		{ "get",       pkt_get },
-		{ NULL, NULL }
-	};
-	lua_register_meta(L, wrap, META_PKT);
-	return 0;
-}
-
-/**
- * Query interface.
- * @note Query is a full userdata, use double pointers.
- */
-
-static int query_qtype(lua_State *L)
-{
-	struct kr_query *qry = CHECK_UDATA(qry, L);
-	lua_pushnumber(L, qry->stype);
-	return 1;
-}
-
-static int query_qclass(lua_State *L)
-{
-	struct kr_query *qry = CHECK_UDATA(qry, L);
-	lua_pushnumber(L, qry->sclass);
-	return 1;	
-}
-
-static int query_qname(lua_State *L)
-{
-	struct kr_query *qry = CHECK_UDATA(qry, L);
-	lua_pushdname(L, qry->sname);
-	return 1;	
-}
-
-static int query_flag(lua_State *L)
-{
-	struct kr_query *qry = CHECK_UDATA(qry, L);
-	if (lua_gettop(L) < 2 || !lua_isnumber(L, 2)) {
-		return 0;
-	}
-	qry->flags |= lua_tointeger(L, 2);
-	return 0;
-}
-
-static int query_clear_flag(lua_State *L)
-{
-	struct kr_query *qry = CHECK_UDATA(qry, L);
-	if (lua_gettop(L) < 2 || !lua_isnumber(L, 2)) {
-		return 0;
-	}
-	qry->flags &= ~lua_tointeger(L, 2);
-	return 0;
-}
-
-static int query_has_flag(lua_State *L)
-{
-	struct kr_query *qry = CHECK_UDATA(qry, L);
-	if (lua_gettop(L) < 2 || !lua_isnumber(L, 2)) {
-		return 0;
-	}
-	lua_pushboolean(L, qry->flags & lua_tointeger(L, 2));
-	return 1;
-}
-
-static int query_current(lua_State *L)
-{
-	struct kr_request *req = CHECK_UDATA(req, L);
-	lua_pushlightuserdata(L, kr_rplan_current(&req->rplan));
-	return 1;
-}
-
-static int query_resolved(lua_State *L)
-{
-	struct kr_request *req = CHECK_UDATA(req, L);
-	lua_pushlightuserdata(L, TAIL(req->rplan.resolved));
-	return 1;
-}
-
-static int qry_meta_register(lua_State *L)
-{
-	static const luaL_Reg wrap[] = {
-		{ "qtype",      query_qtype  },
-		{ "qclass",     query_qclass },
-		{ "qname",      query_qname  },
-		{ "flag",       query_flag   },
-		{ "clear_flag", query_clear_flag },
-		{ "has_flag",   query_has_flag },
-		{ NULL, NULL }
-	};
-	lua_getfield(L, -1, "query");
-	for (const luaL_Reg *reg = wrap; reg->name; ++reg) {
-		lua_pushcfunction(L, reg->func);
-		lua_setfield(L, -2, reg->name);
-	}
-	lua_pop(L, 1);
-	return 0;
-}
-
-int lib_kres(lua_State *L)
-{
-	static const luaL_Reg lib[] = {
-		{ "query_current",    query_current },
-		{ "query_resolved",   query_resolved },
-		{ NULL, NULL }
-	};
-	/* Create module and register functions */
-	register_lib(L, "kres", lib);
-	/* Register states */
-	WRAP_CONST(L, NOOP,    KNOT_STATE_);
-	WRAP_CONST(L, CONSUME, KNOT_STATE_);
-	WRAP_CONST(L, PRODUCE, KNOT_STATE_);
-	WRAP_CONST(L, DONE,    KNOT_STATE_);
-	WRAP_CONST(L, FAIL,    KNOT_STATE_);
-	/* Register packet sections */
-	WRAP_CONST(L, ANSWER,     KNOT_);
-	WRAP_CONST(L, AUTHORITY,  KNOT_);
-	WRAP_CONST(L, ADDITIONAL, KNOT_);
-	/* Register RCODE, OPCODE */
-	WRAP_LUT(L, "rcode",  knot_rcode_names);
-	WRAP_LUT(L, "type", rrtype_names);
-	WRAP_LUT(L, "class",  rrclass_names);
-	WRAP_LUT(L, "opcode", knot_opcode_names);
-	WRAP_LUT(L, "wire",   wire_flag_names);
-	WRAP_LUT(L, "query",  query_flag_names);
-	/* Register metatables */
-	pkt_meta_register(L);
-	qry_meta_register(L);
-	return 1;	
-}
diff --git a/daemon/bindings/kres.h b/daemon/bindings/kres.h
deleted file mode 100644
index d26920622f9467540648421ab2b0facd2423a675..0000000000000000000000000000000000000000
--- a/daemon/bindings/kres.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*  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/>.
- */
-
-/**
- * Lua-friendly bindings to resolver library parts,
- * notably packet parsing and interpretation and operation on primitives like domain names.
- */
-#pragma once
-
-#include "daemon/bindings.h"
-
-/* Metatable list */
-#define META_PKT   "kres.meta_pkt"
-
-/**
- * Load libkres library.
- * @param  L scriptable
- * @return   number of packages to load
- */
-int lib_kres(lua_State *L);
\ No newline at end of file
diff --git a/daemon/daemon.mk b/daemon/daemon.mk
index 6088050782d8f303670d5bc1921a30438e6ff688..7b15af7fc6ac3dbeb88782bb91266c2cd4653a1f 100644
--- a/daemon/daemon.mk
+++ b/daemon/daemon.mk
@@ -9,13 +9,15 @@ kresd_SOURCES := \
 	daemon/worker.c      \
 	daemon/bindings.c    \
 	daemon/ffimodule.c   \
-	daemon/bindings/kres.c \
 	daemon/main.c
 
 # Embed resources
 daemon/engine.o: daemon/lua/sandbox.inc daemon/lua/config.inc
 %.inc: %.lua
 	@$(call quiet,XXD,$<) $< > $@
+# Installed FFI bindings
+bindings-install: daemon/lua/kres.lua
+	$(INSTALL) $< $(PREFIX)/$(MODULEDIR)
 
 # Dependencies
 kresd_DEPEND := $(libkres)
@@ -28,7 +30,7 @@ endif
 
 # Targets
 daemon: $(kresd)
-daemon-install: kresd-install
+daemon-install: kresd-install bindings-install
 daemon-clean: kresd-clean
 	@$(RM) daemon/lua/*.inc
 
diff --git a/daemon/engine.c b/daemon/engine.c
index e91aef6eb0c5eafbdaf73f141164451f50589609..bf47aa472d9bf2cd6066df90612f07060a192df0 100644
--- a/daemon/engine.c
+++ b/daemon/engine.c
@@ -244,6 +244,7 @@ static int init_state(struct engine *engine)
 		return kr_error(ENOMEM);
 	}
 	/* Initialize used libraries. */
+	lua_gc(engine->L, LUA_GCSTOP, 0);
 	luaL_openlibs(engine->L);
 	/* Global functions */
 	lua_pushcfunction(engine->L, l_help);
@@ -397,6 +398,12 @@ int engine_start(struct engine *engine)
 		return ret;
 	}
 
+	/* Clean up stack and restart GC */
+	lua_settop(engine->L, 0);
+	lua_gc(engine->L, LUA_GCCOLLECT, 0);
+	lua_gc(engine->L, LUA_GCSETSTEPMUL, 50);
+	lua_gc(engine->L, LUA_GCSETPAUSE, 400);
+	lua_gc(engine->L, LUA_GCRESTART, 0);
 	return kr_ok();
 }
 
@@ -434,7 +441,12 @@ int engine_register(struct engine *engine, const char *name)
 	if (engine == NULL || name == NULL) {
 		return kr_error(EINVAL);
 	}
-
+	/* Check priority modules */
+	bool is_priority = false;
+	if (name[0] == '<') {
+		is_priority = true;
+		name += 1;
+	}
 	/* Make sure module is unloaded */
 	(void) engine_unregister(engine, name);
 	/* Attempt to load binary module */
@@ -452,11 +464,16 @@ int engine_register(struct engine *engine, const char *name)
 		free(module);
 		return ret;
 	}
-
 	if (array_push(engine->modules, module) < 0) {
 		engine_unload(engine, module);
 		return kr_error(ENOMEM);
 	}
+	/* Push to front if priority module */
+	if (is_priority) {
+		struct kr_module **arr = engine->modules.at;
+		memmove(&arr[1], &arr[0], sizeof(*arr) * (engine->modules.len - 1));
+		arr[0] = module;
+	}
 
 	/* Register properties */
 	if (module->props) {
diff --git a/daemon/ffimodule.c b/daemon/ffimodule.c
index e8fbe2f547d87496cea36692637e418958b9cfc1..d17c687498d162e69ca64a0cd1634f02b7907178 100644
--- a/daemon/ffimodule.c
+++ b/daemon/ffimodule.c
@@ -19,7 +19,6 @@
 #include "daemon/engine.h"
 #include "daemon/ffimodule.h"
 #include "daemon/bindings.h"
-#include "daemon/bindings/kres.h"
 #include "lib/module.h"
 #include "lib/layer.h"
 
@@ -29,17 +28,22 @@
 #define l_resume(L, argc) lua_resume((L), (argc))
 #endif
 
-/** @internal Set metatable on the object on stack. */
-static void set_metatable(lua_State *L, const char *tname)
-{
-	luaL_getmetatable(L, tname);
-	lua_setmetatable(L, -2);
-}
+/** @internal Slots for layer callbacks.
+  * Each slot ID corresponds to Lua reference in module API. */
+enum {
+	SLOT_begin = 0,
+	SLOT_reset,
+	SLOT_finish,
+	SLOT_consume,
+	SLOT_produce,
+	SLOT_count
+};
+#define SLOT_size sizeof(int)
 
 /** @internal Helper for retrieving the right function entrypoint. */
 static inline lua_State *l_ffi_preface(struct kr_module *module, const char *call) {
 	lua_State *L = module->lib;
-	lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t)module->data);
+	lua_getglobal(L, module->name);
 	lua_getfield(L, -1, call);
 	lua_remove(L, -2);
 	if (lua_isnil(L, -1)) {
@@ -83,16 +87,12 @@ static inline int l_ffi_call(lua_State *L, int argc)
 		lua_pop(L, 1);
 		return kr_error(EIO);
 	}
-
-	int n = lua_gettop(L);
-	if (n > 0) {
-		if (lua_isthread(L, -1)) { /* Continuations */
-			status = l_ffi_defer(lua_tothread(L, -1));
-		} else if (lua_isnumber(L, -1)) { /* Return code */
-			status = lua_tonumber(L, -1);
-		}
-		lua_pop(L, 1);
+	if (lua_isnumber(L, -1)) { /* Return code */
+		status = lua_tonumber(L, -1);
+	} else if (lua_isthread(L, -1)) { /* Continuations */
+		status = l_ffi_defer(lua_tothread(L, -1));
 	}
+	lua_pop(L, 1);
 	return status;
 }
 
@@ -105,6 +105,13 @@ static int l_ffi_init(struct kr_module *module)
 	return l_ffi_call(L, 1);
 }
 
+/** @internal Unregister layer callback reference from registry. */
+#define LAYER_UNREGISTER(L, api, name) do { \
+	int *cb_slot = (int *)((char *)api + sizeof(knot_layer_api_t)); \
+	if (cb_slot[SLOT_ ## name] > 0) \
+		luaL_unref(L, LUA_REGISTRYINDEX, cb_slot[SLOT_ ## name]); \
+} while(0)
+
 static int l_ffi_deinit(struct kr_module *module)
 {
 	/* Deinit the module in Lua (if possible) */
@@ -113,45 +120,42 @@ static int l_ffi_deinit(struct kr_module *module)
 	if (l_ffi_preface(module, "deinit")) {
 		ret = l_ffi_call(L, 1);
 	}
-	/* Free the layer API wrapper */
-	lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t)module->data);
-	lua_getfield(L, -1, "_layer_capi");
-	free(lua_touserdata(L, -1));
-	lua_pop(L, 2);
-	/* Unref module and unset 'lib', so the module
-	 * interface doesn't attempt to close it.
-	 */
-	luaL_unref(L, LUA_REGISTRYINDEX, (intptr_t)module->data);
+	/* Free the layer API wrapper (unconst it) */
+	knot_layer_api_t* api = module->data;
+	if (api) {
+		LAYER_UNREGISTER(L, api, begin);
+		LAYER_UNREGISTER(L, api, finish);
+		LAYER_UNREGISTER(L, api, consume);
+		LAYER_UNREGISTER(L, api, produce);
+		LAYER_UNREGISTER(L, api, reset);
+		free(api);
+	}
 	module->lib = NULL;
 	return ret;
 }
+#undef LAYER_UNREGISTER
 
 /** @internal Helper for retrieving layer Lua function by name. */
-#define LAYER_FFI_CALL(ctx, name) \
-	struct kr_module *module = (ctx)->api->data; \
-	lua_State *L = module->lib; \
-	lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t)module->data); \
-	lua_getfield(L, -1, "layer"); \
-	lua_remove(L, -2); \
-	lua_getfield(L, -1, (name)); \
-	lua_remove(L, -2); \
-	if (lua_isnil(L, -1)) { \
-		lua_pop(L, 1); \
+#define LAYER_FFI_CALL(ctx, slot) \
+	int *cb_slot = (int *)((char *)(ctx)->api + sizeof(knot_layer_api_t)); \
+	if (cb_slot[SLOT_ ## slot] <= 0) { \
 		return ctx->state; \
 	} \
+	struct kr_module *module = (ctx)->api->data; \
+	lua_State *L = module->lib; \
+	lua_rawgeti(L, LUA_REGISTRYINDEX, cb_slot[SLOT_ ## slot]); \
 	lua_pushnumber(L, ctx->state)
 
 static int l_ffi_layer_begin(knot_layer_t *ctx, void *module_param)
 {
-	ctx->data = module_param;
-	LAYER_FFI_CALL(ctx, "begin");
-	lua_pushlightuserdata(L, module_param);
+	LAYER_FFI_CALL(ctx, begin);
+	lua_pushlightuserdata(L, ctx->data);
 	return l_ffi_call(L, 2);
 }
 
 static int l_ffi_layer_reset(knot_layer_t *ctx)
 {
-	LAYER_FFI_CALL(ctx, "reset");
+	LAYER_FFI_CALL(ctx, reset);
 	lua_pushlightuserdata(L, ctx->data);
 	return l_ffi_call(L, 2);
 }
@@ -159,10 +163,9 @@ static int l_ffi_layer_reset(knot_layer_t *ctx)
 static int l_ffi_layer_finish(knot_layer_t *ctx)
 {
 	struct kr_request *req = ctx->data;
-	LAYER_FFI_CALL(ctx, "finish");
+	LAYER_FFI_CALL(ctx, finish);
 	lua_pushlightuserdata(L, req);
 	lua_pushlightuserdata(L, req->answer);
-	set_metatable(L, META_PKT);
 	return l_ffi_call(L, 3);
 }
 
@@ -171,81 +174,67 @@ static int l_ffi_layer_consume(knot_layer_t *ctx, knot_pkt_t *pkt)
 	if (ctx->state & KNOT_STATE_FAIL) {
 		return ctx->state; /* Already failed, skip */
 	}
-	LAYER_FFI_CALL(ctx, "consume");
+	LAYER_FFI_CALL(ctx, consume);
 	lua_pushlightuserdata(L, ctx->data);
 	lua_pushlightuserdata(L, pkt);
-	set_metatable(L, META_PKT);
 	return l_ffi_call(L, 3);
 }
 
 static int l_ffi_layer_produce(knot_layer_t *ctx, knot_pkt_t *pkt)
 {
-	if (ctx->state & (KNOT_STATE_FAIL|KNOT_STATE_DONE)) {
+	if (ctx->state & (KNOT_STATE_FAIL)) {
 		return ctx->state; /* Already failed or done, skip */
 	}
-	LAYER_FFI_CALL(ctx, "produce");
-	lua_pushlightuserdata(L, ctx->data);
-	lua_pushlightuserdata(L, pkt);
-	set_metatable(L, META_PKT);
-	return l_ffi_call(L, 3);
-}
-
-static int l_ffi_layer_fail(knot_layer_t *ctx, knot_pkt_t *pkt)
-{
-	LAYER_FFI_CALL(ctx, "fail");
+	LAYER_FFI_CALL(ctx, produce);
 	lua_pushlightuserdata(L, ctx->data);
 	lua_pushlightuserdata(L, pkt);
-	set_metatable(L, META_PKT);
 	return l_ffi_call(L, 3);
 }
+#undef LAYER_FFI_CALL
 
-/** Conditionally register layer trampoline */
+/** @internal Conditionally register layer trampoline
+  * @warning Expects 'module.layer' to be on top of Lua stack. */
 #define LAYER_REGISTER(L, api, name) do { \
-	lua_getfield((L), -1, "layer"); \
+	int *cb_slot = (int *)((char *)api + sizeof(knot_layer_api_t)); \
 	lua_getfield((L), -1, #name); \
-	if (!lua_isnil((L), -1)) (api)->name = l_ffi_layer_ ## name; \
-	lua_pop((L), 2); \
+	if (!lua_isnil((L), -1)) { \
+		(api)->name = l_ffi_layer_ ## name; \
+		cb_slot[SLOT_ ## name] = luaL_ref((L), LUA_REGISTRYINDEX); \
+	} else { \
+		lua_pop((L), 1); \
+	} \
 } while(0)
 
-/** @internal Retrieve C layer api wrapper. */
-static const knot_layer_api_t* l_ffi_layer(struct kr_module *module)
+/** @internal Create C layer api wrapper. */
+static knot_layer_api_t *l_ffi_layer_create(lua_State *L, struct kr_module *module)
 {
-	lua_State *L = module->lib;
-	lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t)module->data);
-	lua_getfield(L, -1, "_layer_capi");
-	knot_layer_api_t *api = lua_touserdata(L, -1);
-	lua_pop(L, 1);
-	if (!api) {
-		/* Fabricate layer API wrapping the Lua functions */
-		api = malloc(sizeof(*api));
-		if (api) {
-			memset(api, 0, sizeof(*api));
-			/* Begin is always set, as it initializes layer baton. */
-			api->begin = l_ffi_layer_begin;
-			LAYER_REGISTER(L, api, finish);
-			LAYER_REGISTER(L, api, consume);
-			LAYER_REGISTER(L, api, produce);
-			LAYER_REGISTER(L, api, reset);
-			LAYER_REGISTER(L, api, fail);
-			api->data = module;
-		}
-		/* Store the api in the registry. */
-		lua_pushlightuserdata(L, api);
-		lua_setfield(L, -2, "_layer_capi");
+	/* Fabricate layer API wrapping the Lua functions
+	 * reserve slots after it for references to Lua callbacks. */
+	const size_t api_length = sizeof(knot_layer_api_t) + (SLOT_count * SLOT_size);
+	knot_layer_api_t *api = malloc(api_length);
+	if (api) {
+		memset(api, 0, api_length);
+		LAYER_REGISTER(L, api, begin);
+		LAYER_REGISTER(L, api, finish);
+		LAYER_REGISTER(L, api, consume);
+		LAYER_REGISTER(L, api, produce);
+		LAYER_REGISTER(L, api, reset);
+		/* Begin is always set, as it initializes layer baton. */
+		api->begin = l_ffi_layer_begin;
+		api->data = module;
 	}
-	lua_pop(L, 1); /* Clear module table */
 	return api;
 }
 
+/** @internal Retrieve C layer api wrapper. */
+static const knot_layer_api_t *l_ffi_layer(struct kr_module *module)
+{
+	if (module) {
+		return (const knot_layer_api_t *)module->data;
+	}
+	return NULL;
+}
 #undef LAYER_REGISTER
-#undef LAYER_FFI_CALL
-
-/** @internal Helper macro for function presence check. */
-#define REGISTER_FFI_CALL(L, attr, name, cb) do { \
-	lua_getfield((L), -1, (name)); \
-	if (!lua_isnil((L), -1)) { attr = cb; } \
-	lua_pop((L), 1); \
-} while (0)
 
 int ffimodule_register_lua(struct engine *engine, struct kr_module *module, const char *name)
 {
@@ -266,14 +255,16 @@ int ffimodule_register_lua(struct engine *engine, struct kr_module *module, cons
 	module->name = strdup(name);
 	module->init = &l_ffi_init;
 	module->deinit = &l_ffi_deinit;
-	REGISTER_FFI_CALL(L, module->layer,  "layer",  &l_ffi_layer);
-	module->data = (void *)(intptr_t)luaL_ref(L, LUA_REGISTRYINDEX);
+	/* Bake layer API if defined in module */
+	lua_getfield(L, -1, "layer");
+	if (!lua_isnil(L, -1)) {
+		module->layer = &l_ffi_layer;
+		module->data = l_ffi_layer_create(L, module);
+	}
 	module->lib = L;
-	lua_pop(L, 1); /* Clear the module global */
+	lua_pop(L, 2); /* Clear the layer + module global */
 	if (module->init) {
 		return module->init(module);
 	}
 	return kr_ok();
 }
-
-#undef REGISTER_FFI_CALL
\ No newline at end of file
diff --git a/daemon/lua/kres.lua b/daemon/lua/kres.lua
new file mode 100644
index 0000000000000000000000000000000000000000..60591819e88c9fe9c586c7b529662bd6f69257f9
--- /dev/null
+++ b/daemon/lua/kres.lua
@@ -0,0 +1,208 @@
+-- LuaJIT ffi bindings for libkres, a DNS resolver library.
+-- @note Since it's statically compiled, it expects to find the symbols in the C namespace.
+
+local ffi = require('ffi')
+local bit = require('bit')
+local csym = ffi.C
+local knot = ffi.load('knot')
+ffi.cdef[[
+
+/*
+ * Record types and classes.
+ */
+struct rr_class {
+	static const int IN         =   1;
+	static const int CH         =   3;
+	static const int NONE       = 254;
+	static const int ANY        = 255;
+};
+struct rr_type {
+	static const int A          =   1;
+	static const int NS         =   2;
+	static const int CNAME      =   5;
+	static const int SOA        =   6;
+	static const int PTR        =  12;
+	static const int HINFO      =  13;
+	static const int MINFO      =  14;
+	static const int MX         =  15;
+	static const int TXT        =  16;
+	static const int RP         =  17;
+	static const int AFSDB      =  18;
+	static const int RT         =  21;
+	static const int SIG        =  24;
+	static const int KEY        =  25;
+	static const int AAAA       =  28;
+	static const int LOC        =  29;
+	static const int SRV        =  33;
+	static const int NAPTR      =  35;
+	static const int KX         =  36;
+	static const int CERT       =  37;
+	static const int DNAME      =  39;
+	static const int OPT        =  41;
+	static const int APL        =  42;
+	static const int DS         =  43;
+	static const int SSHFP      =  44;
+	static const int IPSECKEY   =  45;
+	static const int RRSIG      =  46;
+	static const int NSEC       =  47;
+	static const int DNSKEY     =  48;
+	static const int DHCID      =  49;
+	static const int NSEC3      =  50;
+	static const int NSEC3PARAM =  51;
+	static const int TLSA       =  52;
+	static const int CDS        =  59;
+	static const int CDNSKEY    =  60;
+	static const int SPF        =  99;
+	static const int NID        = 104;
+	static const int L32        = 105;
+	static const int L64        = 106;
+	static const int LP         = 107;
+	static const int EUI48      = 108;
+	static const int EUI64      = 109;
+	static const int TKEY       = 249;
+	static const int TSIG       = 250;
+	static const int IXFR       = 251;
+	static const int AXFR       = 252;
+	static const int ANY        = 255;
+};
+struct pkt_section {
+	static const int ANSWER     = 0;
+	static const int AUTHORITY  = 1;
+	static const int ADDITIONAL = 2;	
+};
+struct pkt_rcode {
+	static const int NOERROR    =  0;
+	static const int FORMERR    =  1;
+	static const int SERVFAIL   =  2;
+	static const int NXDOMAIN   =  3;
+	static const int NOTIMPL    =  4;
+	static const int REFUSED    =  5;
+	static const int YXDOMAIN   =  6;
+	static const int YXRRSET    =  7;
+	static const int NXRRSET    =  8;
+	static const int NOTAUTH    =  9;
+	static const int NOTZONE    = 10;
+	static const int BADVERS    = 16;
+};
+/*
+ * Data structures
+ */
+
+/* libknot */
+typedef struct node {
+  struct node *next, *prev;
+} node_t;
+typedef uint8_t knot_dname_t;
+typedef struct {
+	uint8_t *wire;
+	size_t size;
+	size_t max_size;
+	size_t parsed;
+	uint16_t reserved;
+	uint16_t qname_size;
+	uint16_t rrset_count;
+	uint16_t flags;
+	uint8_t _stub[]; /* Do not touch */
+} knot_pkt_t;
+
+/* libkres */
+struct kr_query {
+	node_t _node;
+	struct kr_query *parent;
+	knot_dname_t *sname;
+	uint16_t type;
+	uint16_t class;
+	uint16_t id;
+	uint16_t flags;
+	unsigned secret;
+	uint8_t _stub[]; /* Do not touch */
+};
+struct kr_rplan {
+	uint8_t _stub[]; /* Do not touch */
+};
+struct kr_request {
+	struct kr_context *_ctx;
+	knot_pkt_t *answer;
+	uint32_t options;
+	int state;
+	uint8_t _stub[]; /* Do not touch */
+};
+
+/* libknot API
+ */
+
+/* Domain names */
+/* Resource records */
+/* Packet */
+const knot_dname_t *knot_pkt_qname(const knot_pkt_t *pkt);
+uint16_t knot_pkt_qtype(const knot_pkt_t *pkt);
+uint16_t knot_pkt_qclass(const knot_pkt_t *pkt);
+int knot_pkt_begin(knot_pkt_t *pkt, int section_id);
+int knot_pkt_put_question(knot_pkt_t *pkt, const knot_dname_t *qname, uint16_t qclass, uint16_t qtype);
+
+/* libkres API
+ */
+
+/* Resolution request */
+struct kr_rplan *kr_resolve_plan(struct kr_request *request);
+/* Resolution plan */
+struct kr_query *kr_rplan_current(struct kr_rplan *rplan);
+/* Query */
+/* Utils */
+unsigned kr_rand_uint(unsigned max);
+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);
+]]
+
+-- Metatype for packet
+local knot_pkt_t = ffi.typeof('knot_pkt_t')
+ffi.metatype( knot_pkt_t, {
+	__index = {
+		qname = function(pkt) return ffi.string(knot.knot_pkt_qname(pkt)) end,
+		qclass = function(pkt) return knot.knot_pkt_qclass(pkt) end,
+		qtype  = function(pkt) return knot.knot_pkt_qtype(pkt) end,
+		rcode = function (pkt, val)
+			if val then
+				pkt.wire[3] = bit.bor(bit.band(pkt.wire[3], 0xf0), val)
+			end
+			return bit.band(pkt.wire[3], 0x0f)
+		end,
+		begin = function (pkt, section) return knot.knot_pkt_begin(pkt, section) end,
+		put = function (pkt, owner, ttl, rclass, rtype, rdata)
+			return csym.kr_pkt_put(pkt, owner, ttl, rclass, rtype, rdata, string.len(rdata))
+		end
+	},
+})
+-- Metatype for query
+local kr_query_t = ffi.typeof('struct kr_query')
+ffi.metatype( kr_query_t, {
+	__index = {
+		name = function(qry) return ffi.string(qry.sname) end,
+	},
+})
+-- Metatype for request
+local kr_request_t = ffi.typeof('struct kr_request')
+ffi.metatype( kr_request_t, {
+	__index = {
+		current = function(req)
+			assert(req)
+			return csym.kr_rplan_current(csym.kr_resolve_plan(req))
+		end,
+	},
+})
+
+-- Module API
+local kres = {
+	-- Constants
+	class = ffi.new('struct rr_class'),
+	type = ffi.new('struct rr_type'),
+	section = ffi.new('struct pkt_section'),
+	rcode = ffi.new('struct pkt_rcode'),
+	NOOP = 0, CONSUME = 1, PRODUCE = 2, DONE = 4, FAIL = 8,
+	-- Metatypes
+	pkt_t = function (udata) return ffi.cast('knot_pkt_t *', udata) end,
+	request_t = function (udata) return ffi.cast('struct kr_request *', udata) end,
+	-- Global API functions
+}
+
+return kres
\ No newline at end of file
diff --git a/daemon/main.c b/daemon/main.c
index cb72d48a18f593380d9f1f89bb7b593a8f492738..ba74fdd5b004fe0ce5f6bf2509c6494fb18df705 100644
--- a/daemon/main.c
+++ b/daemon/main.c
@@ -28,7 +28,6 @@
 #include "daemon/worker.h"
 #include "daemon/engine.h"
 #include "daemon/bindings.h"
-#include "daemon/bindings/kres.h"
 
 /*
  * Globals
@@ -137,7 +136,6 @@ static struct worker_ctx *init_worker(uv_loop_t *loop, struct engine *engine, mm
 	engine_lualib(engine, "net",     lib_net);
 	engine_lualib(engine, "cache",   lib_cache);
 	engine_lualib(engine, "event",   lib_event);
-	engine_lualib(engine, "kres",    lib_kres);
 	engine_lualib(engine, "worker",  lib_worker);
 
 	/* Create main worker. */
@@ -294,7 +292,6 @@ int main(int argc, char **argv)
 		ret = run_worker(loop, &engine);
 	}
 	/* Cleanup. */
-	fprintf(stderr, "\n[system] quitting\n");
 	engine_deinit(&engine);
 	worker_reclaim(worker);
 	mp_delete(pool.ctx);
diff --git a/daemon/worker.c b/daemon/worker.c
index 3cf883d1e3801690a10e0cfc1bb65b973b8a6ca7..cd61e2aa4f64a1f4d6c9c6a5aab1d4e44c0f7084 100644
--- a/daemon/worker.c
+++ b/daemon/worker.c
@@ -15,6 +15,7 @@
  */
 
 #include <uv.h>
+#include <lua.h>
 #include <libknot/packet/pkt.h>
 #include <libknot/internal/net.h>
 #include <contrib/ucw/lib.h>
@@ -168,14 +169,16 @@ static void qr_task_free(uv_handle_t *handle)
 	} else {
 		mp_delete(mp_context);
 	}
-#if defined(__GLIBC__) && defined(_GNU_SOURCE)
 	/* Decommit memory every once in a while */
 	static int mp_delete_count = 0;
-	if (++mp_delete_count == 100 * MP_FREELIST_SIZE) {
+	if (++mp_delete_count == 100000) {
+		lua_gc(worker->engine->L, LUA_GCCOLLECT, 0);
+#if defined(__GLIBC__) && defined(_GNU_SOURCE)
 		malloc_trim(0);
+#endif
 		mp_delete_count = 0;
 	}
-#endif
+
 	/* Update stats */
 	worker->stats.concurrent -= 1;
 }
diff --git a/doc/build.rst b/doc/build.rst
index 38faf0a76e603b1e579cac53a00e49819055dffc..32e04d5a25259bedd94b304ea6939c16986376d6 100644
--- a/doc/build.rst
+++ b/doc/build.rst
@@ -31,7 +31,7 @@ The following is a list of software required to build Knot DNS Resolver from sou
    "`pkg-config`_", "*all*", "*(build only)* [#]_"
    "C compiler", "*all*", "*(build only)* [#]_"
    "libknot_ 2.0+", "*all*", "Knot DNS library."
-   "Lua_ 5.1+", "``daemon``", "Embeddable scripting language (LuaJIT_ is preferred)."
+   "LuaJIT_ 2.0+", "``daemon``", "Embedded scripting language (Lua_ 5.1+ with limitations)."
    "libuv_ 1.0+", "``daemon``", "Multiplatform I/O and services."
 
 There are also *optional* packages that enable specific functionality in Knot DNS Resolver, they are useful mainly for developers to build documentation and tests.
@@ -42,7 +42,7 @@ There are also *optional* packages that enable specific functionality in Knot DN
    "libmemcached_", "``modules/memcached``", "To build memcached backend module."
    "hiredis_", "``modules/redis``", "To build redis backend module."
    "cmocka_", "``unit tests``", "Unit testing framework."
-   "Python_", "``integration tests``", "For scripting tests, C header files are required (``python-dev``)"
+   "Python_", "``integration tests``", "For test scripts."
    "GCCGO_",  "``modules/go``", "For building Go modules, see modules documentation."
    "Doxygen_", "``documentation``", "Generating API documentation."
    "Sphinx_", "``documentation``", "Building this HTML/PDF documentation."
@@ -72,15 +72,47 @@ You can hack on the container by changing the container entrypoint to shell like
 Building from sources 
 ---------------------
 
-The Knot DNS Resolver depends on the development version of the Knot DNS library, and a reasonably recent version of `libuv`.
-Several dependencies may not be in the packages yet, the script pulls and installs all dependencies in a chroot.
+The Knot DNS Resolver depends on the the Knot DNS library, recent version of libuv_, and LuaJIT_.
 
 .. code-block:: bash
 
    $ make info # See what's missing
 
+When you have all the dependencies ready, you can build and install.
+
+.. code-block:: bash
+
+   $ make PREFIX="/usr/local"
+   $ make install
+
+.. note:: Always build with ``PREFIX`` if you want to install, as it is hardcoded in the executable for module search path.
+
+Alternatively you can build only specific parts of the project, i.e. ``library``.
+
+.. code-block:: bash
+
+   $ make lib
+   $ make lib-install
+
+.. note:: Documentation is not built by default, run ``make doc`` to build it.
+
+Debug build
+-----------
+
+For debugging or tinkering purposes, it's useful to build the daemon with the debug messages enabled.
+
+.. code-block:: bash
+
+   $ CFLAGS="-O0 -g -DWITH_DEBUG" make
+
+.. warning:: If you want to track specific things like i.e. number of subrequests for given zone in production, use Lua modules or write custom layers rather then depending on debug output.
+
+Building dependencies
+~~~~~~~~~~~~~~~~~~~~~
+
+Several dependencies may not be in the packages yet, the script pulls and installs all dependencies in a chroot.
 You can avoid rebuilding dependencies by specifying `BUILD_IGNORE` variable, see the Dockerfile_ for example.
-Usually you only really need to rebuild `libknot`.
+Usually you only really need to rebuild libknot_.
 
 .. code-block:: bash
 
@@ -94,31 +126,12 @@ Usually you only really need to rebuild `libknot`.
 
    .. code-block:: bash
 
-      $ make check libknot_CFLAGS="-I/opt/include" libknot_LIBS="-L/opt/lib -lknot -lknot-int -ldnssec"
+      $ make libknot_CFLAGS="-I/opt/include" libknot_LIBS="-L/opt/lib -lknot -lknot-int -ldnssec"
 
 .. warning:: If the dependencies lie outside of library search path, you need to add them somehow.
    Try ``LD_LIBRARY_PATH`` on Linux/BSD, and ``DYLD_FALLBACK_LIBRARY_PATH`` on OS X.
    Otherwise you need to add the locations to linker search path.
 
-When you have all the dependencies ready, you can build, test and install.
-
-.. code-block:: bash
-
-   $ make PREFIX="/usr/local"
-   $ make check
-   $ make install
-
-.. note:: Always build with ``PREFIX`` if you want to install, as it is hardcoded in the executable for module search path.
-
-Alternatively you can build only specific parts of the project, i.e. ``library``.
-
-.. code-block:: bash
-
-   $ make lib
-   $ make lib-install
-
-.. note:: Documentation is not built by default, run ``make doc`` to build it.
-
 Building extras
 ~~~~~~~~~~~~~~~
 
diff --git a/lib/layer.h b/lib/layer.h
index 2df30b7e42be90165f627ab06a4ec9e8d42b4b03..db358c7e949b0e37dd471c2637be9dd9cdb895b1 100644
--- a/lib/layer.h
+++ b/lib/layer.h
@@ -19,7 +19,7 @@
 #include "lib/defines.h"
 #include "lib/resolve.h"
 
-#ifndef NDEBUG
+#ifdef WITH_DEBUG
 /** @internal Print a debug message related to resolution. */
  #define QRDEBUG(query, cls, fmt, ...) do { \
     unsigned _ind = 0; \
diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c
index fb4e38c6a58b3e39a5914c6a88067bc1b680ee37..ef612950ac5533b1f78b33b200e3204d8bb99d2c 100644
--- a/lib/layer/iterate.c
+++ b/lib/layer/iterate.c
@@ -351,7 +351,9 @@ static int finish(knot_layer_t *ctx) { return KNOT_STATE_NOOP; }
 /* Set resolution context and parameters. */
 static int begin(knot_layer_t *ctx, void *module_param)
 {
-	ctx->data = module_param;
+	if (ctx->state & (KNOT_STATE_DONE|KNOT_STATE_FAIL)) {
+		return ctx->state;
+	}
 	return reset(ctx);
 }
 
@@ -446,10 +448,11 @@ static int resolve(knot_layer_t *ctx, knot_pkt_t *pkt)
 		return KNOT_STATE_DONE;
 	}
 
-	/* Check response code. */
-#ifndef NDEBUG
+#ifdef WITH_DEBUG
 	lookup_table_t *rcode = lookup_by_id(knot_rcode_names, knot_wire_get_rcode(pkt->wire));
 #endif
+
+	/* Check response code. */
 	switch(knot_wire_get_rcode(pkt->wire)) {
 	case KNOT_RCODE_NOERROR:
 	case KNOT_RCODE_NXDOMAIN:
diff --git a/lib/layer/pktcache.c b/lib/layer/pktcache.c
index eb62571cb84f0899956cbbaff83d2bc880d8b1d2..0193772f902cf6a2addf78f90f0941b9625c5ad1 100644
--- a/lib/layer/pktcache.c
+++ b/lib/layer/pktcache.c
@@ -31,12 +31,6 @@ static inline uint8_t get_tag(knot_pkt_t *pkt)
 	return knot_pkt_has_dnssec(pkt) ? KR_CACHE_SEC : KR_CACHE_PKT;
 }
 
-static int begin(knot_layer_t *ctx, void *module_param)
-{
-	ctx->data = module_param;
-	return ctx->state;
-}
-
 static uint32_t limit_ttl(uint32_t ttl)
 {
 	/* @todo Configurable limit */
@@ -217,7 +211,6 @@ static int stash(knot_layer_t *ctx, knot_pkt_t *pkt)
 const knot_layer_api_t *pktcache_layer(struct kr_module *module)
 {
 	static const knot_layer_api_t _layer = {
-		.begin   = &begin,
 		.produce = &peek,
 		.consume  = &stash
 	};
diff --git a/lib/layer/rrcache.c b/lib/layer/rrcache.c
index 9d017caf64d2a1d17c44e3acc921406e253d7272..1025d434ed2f171245e91eac8ab7403c6eabe648 100644
--- a/lib/layer/rrcache.c
+++ b/lib/layer/rrcache.c
@@ -28,12 +28,6 @@
 #define DEBUG_MSG(fmt...) QRDEBUG(kr_rplan_current(rplan), " rc ",  fmt)
 #define DEFAULT_MINTTL (5) /* Short-time "no data" retention to avoid bursts */
 
-static int begin(knot_layer_t *ctx, void *module_param)
-{
-	ctx->data = module_param;
-	return ctx->state;
-}
-
 /** Record is expiring if it has less than 1% TTL (or less than 5s) */
 static inline bool is_expiring(const knot_rrset_t *rr, uint32_t drift)
 {
@@ -312,7 +306,6 @@ static int stash(knot_layer_t *ctx, knot_pkt_t *pkt)
 const knot_layer_api_t *rrcache_layer(struct kr_module *module)
 {
 	static const knot_layer_api_t _layer = {
-		.begin = &begin,
 		.produce = &peek,
 		.consume = &stash
 	};
diff --git a/lib/resolve.c b/lib/resolve.c
index 75f0d7bb40bf5f5767dbd05bf409ec5d49e8d5c8..0c5e508cfe245ef75f2d482bf1d0c93acbcae563 100644
--- a/lib/resolve.c
+++ b/lib/resolve.c
@@ -32,9 +32,11 @@
 #define ITERATE_LAYERS(req, func, ...) \
 	for (unsigned i = 0; i < (req)->ctx->modules->len; ++i) { \
 		struct kr_module *mod = (req)->ctx->modules->at[i]; \
-		if (mod->layer) { \
+		if (mod->layer ) { \
 			struct knot_layer layer = {.state = (req)->state, .api = mod->layer(mod), .data = (req)}; \
-			(req)->state = (func)(&layer, ##__VA_ARGS__); \
+			if (layer.api && layer.api->func) { \
+				(req)->state = layer.api->func(&layer, ##__VA_ARGS__); \
+			} \
 		} \
 	}
 
@@ -59,14 +61,6 @@ static void randomized_qname_case(knot_dname_t *qname, unsigned secret)
 	}
 }
 
-/** @internal Subtract time (best effort) */
-float time_diff(struct timeval *begin, struct timeval *end)
-{
-	return (end->tv_sec - begin->tv_sec) * 1000 +
-	       (end->tv_usec - begin->tv_usec) / 1000.0;
-
-}
-
 /** Invalidate current NS/addr pair. */
 static int invalidate_ns(struct kr_rplan *rplan, struct kr_query *qry)
 {
@@ -301,7 +295,7 @@ int kr_resolve(struct kr_context* ctx, knot_pkt_t *answer,
 	struct kr_request request;
 	request.pool = pool;
 	kr_resolve_begin(&request, ctx, answer);
-#ifndef NDEBUG
+#ifdef WITH_DEBUG
 	struct kr_rplan *rplan = &request.rplan; /* for DEBUG_MSG */
 #endif
 	/* Resolve query, iteratively */
@@ -367,8 +361,12 @@ int kr_resolve_query(struct kr_request *request, const knot_dname_t *qname, uint
 	knot_wire_set_ra(answer->wire);
 	knot_wire_set_rcode(answer->wire, KNOT_RCODE_NOERROR);
 
-	/* Expect answer */
-	return KNOT_STATE_PRODUCE;
+	/* Expect answer, pop if satisfied immediately */
+	ITERATE_LAYERS(request, begin, request);
+	if (request->state == KNOT_STATE_DONE) {
+		kr_rplan_pop(rplan, qry);
+	}
+	return request->state;
 }
 
 int kr_resolve_consume(struct kr_request *request, knot_pkt_t *packet)
@@ -404,7 +402,7 @@ int kr_resolve_consume(struct kr_request *request, knot_pkt_t *packet)
 		if (qname_raw && qry->secret != 0) {
 			randomized_qname_case(qname_raw, qry->secret);
 		}
-		ITERATE_LAYERS(request, knot_layer_consume, packet);
+		ITERATE_LAYERS(request, consume, packet);
 	}
 
 	/* Resolution failed, invalidate current NS. */
@@ -427,7 +425,7 @@ int kr_resolve_consume(struct kr_request *request, knot_pkt_t *packet)
 		qry->flags &= ~(QUERY_CACHED|QUERY_TCP);
 	}
 
-	ITERATE_LAYERS(request, knot_layer_reset);
+	ITERATE_LAYERS(request, reset);
 	return kr_rplan_empty(&request->rplan) ? KNOT_STATE_DONE : KNOT_STATE_PRODUCE;
 }
 
@@ -435,27 +433,20 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t
 {
 	struct kr_rplan *rplan = &request->rplan;
 	struct kr_query *qry = kr_rplan_current(rplan);
+	unsigned ns_election_iter = 0;
 	
 	/* No query left for resolution */
 	if (kr_rplan_empty(rplan)) {
 		return KNOT_STATE_FAIL;
 	}
 
-#ifndef NDEBUG
-	unsigned ns_election_iter = 0;
-	char name_str[KNOT_DNAME_MAXLEN], type_str[16];
-	knot_dname_to_str(name_str, qry->sname, sizeof(name_str));
-	knot_rrtype_to_string(qry->stype, type_str, sizeof(type_str));
-	DEBUG_MSG("query '%s %s'\n", type_str, name_str);
-#endif
-
 	/* Resolve current query and produce dependent or finish */
-	ITERATE_LAYERS(request, knot_layer_produce, packet);
+	ITERATE_LAYERS(request, produce, packet);
 	if (request->state != KNOT_STATE_FAIL && knot_wire_get_qr(packet->wire)) {
 		/* Produced an answer, consume it. */
 		qry->secret = 0;
 		request->state = KNOT_STATE_CONSUME;
-		ITERATE_LAYERS(request, knot_layer_consume, packet);
+		ITERATE_LAYERS(request, consume, packet);
 	}
 	switch(request->state) {
 	case KNOT_STATE_FAIL: return request->state; break;
@@ -465,7 +456,7 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t
 		if (qry->flags & QUERY_RESOLVED) {
 			kr_rplan_pop(rplan, qry);
 		}
-		ITERATE_LAYERS(request, knot_layer_reset);
+		ITERATE_LAYERS(request, reset);
 		return kr_rplan_empty(rplan) ? KNOT_STATE_DONE : KNOT_STATE_PRODUCE;
 	}
 
@@ -491,14 +482,17 @@ ns_election:
 	/* If the query has already selected a NS and is waiting for IPv4/IPv6 record,
 	 * elect best address only, otherwise elect a completely new NS.
 	 */
-	assert(++ns_election_iter < KR_ITER_LIMIT);
+	if(++ns_election_iter >= KR_ITER_LIMIT) {
+		DEBUG_MSG("=> couldn't agree NS decision, report this\n");
+		return KNOT_STATE_FAIL;
+	}
 	if (qry->flags & (QUERY_AWAIT_IPV4|QUERY_AWAIT_IPV6)) {
 		kr_nsrep_elect_addr(qry, request->ctx);
 	} else if (!(qry->flags & QUERY_TCP)) { /* Keep address when TCP retransmit. */
 		kr_nsrep_elect(qry, request->ctx);
 		if (qry->ns.score > KR_NS_MAX_SCORE) {
 			DEBUG_MSG("=> no valid NS left\n");
-			ITERATE_LAYERS(request, knot_layer_reset);
+			ITERATE_LAYERS(request, reset);
 			kr_rplan_pop(rplan, qry);
 			return KNOT_STATE_PRODUCE;
 		}
@@ -510,7 +504,7 @@ ns_election:
 			qry->flags &= ~(QUERY_AWAIT_IPV6|QUERY_AWAIT_IPV4|QUERY_TCP);
 			goto ns_election; /* Must try different NS */
 		}
-		ITERATE_LAYERS(request, knot_layer_reset);
+		ITERATE_LAYERS(request, reset);
 		return KNOT_STATE_PRODUCE;
 	}
 
@@ -520,7 +514,7 @@ ns_election:
 		return KNOT_STATE_FAIL;
 	}
 
-#ifndef NDEBUG
+#ifdef WITH_DEBUG
 	char qname_str[KNOT_DNAME_MAXLEN], zonecut_str[KNOT_DNAME_MAXLEN], ns_str[SOCKADDR_STRLEN];
 	knot_dname_to_str(qname_str, knot_pkt_qname(packet), sizeof(qname_str));
 	struct sockaddr *addr = &qry->ns.addr.ip;
@@ -537,10 +531,9 @@ ns_election:
 
 int kr_resolve_finish(struct kr_request *request, int state)
 {
-#ifndef NDEBUG
 	struct kr_rplan *rplan = &request->rplan;
 	DEBUG_MSG("finished: %d, mempool: %zu B\n", state, (size_t) mp_total_size(request->pool.ctx));
-#endif
+
 	/* Finalize answer */
 	if (answer_finalize(request->answer) != 0) {
 		state = KNOT_STATE_FAIL;
@@ -552,8 +545,16 @@ int kr_resolve_finish(struct kr_request *request, int state)
 			knot_wire_set_rcode(answer->wire, KNOT_RCODE_SERVFAIL);
 		}
 	}
-	ITERATE_LAYERS(request, knot_layer_finish);
+	ITERATE_LAYERS(request, finish);
 	/* Clean up. */
-	kr_rplan_deinit(&request->rplan);
+	kr_rplan_deinit(rplan);
 	return KNOT_STATE_DONE;
 }
+
+struct kr_rplan *kr_resolve_plan(struct kr_request *request)
+{
+	if (request) {
+		return &request->rplan;
+	}
+	return NULL;
+}
diff --git a/lib/resolve.h b/lib/resolve.h
index c7e9e0b36c0003ee9ded009cfb73192116b8c1ae..4720eb21241c49fd7bd2eae9958847fecec8cf7c 100644
--- a/lib/resolve.h
+++ b/lib/resolve.h
@@ -125,11 +125,11 @@ struct kr_context
  */
 struct kr_request {
     struct kr_context *ctx;
-    struct kr_rplan rplan;
     knot_pkt_t *answer;
-    mm_ctx_t pool;
     uint32_t options;
     int state;
+    struct kr_rplan rplan;
+    mm_ctx_t pool;
 };
 
 /**
@@ -207,3 +207,11 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t
  * @return         DONE
  */
 int kr_resolve_finish(struct kr_request *request, int state);
+
+/**
+ * Return resolution plan.
+ * @param  request request state
+ * @return         pointer to rplan
+ */
+struct kr_rplan *kr_resolve_plan(struct kr_request *request);
+
diff --git a/lib/rplan.c b/lib/rplan.c
index 11760a99aa20e928ae9126a5196c538da807d161..8aa640ef677aa1778dc1f0ba9dc392fd37536980 100644
--- a/lib/rplan.c
+++ b/lib/rplan.c
@@ -123,7 +123,7 @@ struct kr_query *kr_rplan_push(struct kr_rplan *rplan, struct kr_query *parent,
 	add_tail(&rplan->pending, &qry->node);
 	kr_zonecut_init(&qry->zone_cut, (const uint8_t *)"", rplan->pool);
 
-#ifndef NDEBUG
+#ifdef WITH_DEBUG
 	char name_str[KNOT_DNAME_MAXLEN], type_str[16];
 	knot_dname_to_str(name_str, name, sizeof(name_str));
 	knot_rrtype_to_string(type, type_str, sizeof(type_str));
diff --git a/lib/rplan.h b/lib/rplan.h
index 96785a6f78f4409ec2aa517df6e2bfa2bfc05ad6..f570f749f919454b7aeb6c4d4b6ed4340eb879fd 100644
--- a/lib/rplan.h
+++ b/lib/rplan.h
@@ -55,15 +55,15 @@ extern const lookup_table_t query_flag_names[];
 struct kr_query {
 	node_t node;
 	struct kr_query *parent;
-	struct kr_nsrep ns;
-	struct kr_zonecut zone_cut;
-	struct timeval timestamp;
 	knot_dname_t *sname;
 	uint16_t stype;
 	uint16_t sclass;
 	uint16_t id;
 	uint16_t flags;
 	unsigned secret;
+	struct timeval timestamp;
+	struct kr_nsrep ns;
+	struct kr_zonecut zone_cut;
 };
 
 /**
diff --git a/lib/utils.c b/lib/utils.c
index 3b1f36b5d29d7a51097bf75d7f18ea68fda41c79..35b52717013bf28a639a91999a38bfc93e677f1c 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -156,3 +156,20 @@ int mm_reserve(void *baton, char **mem, size_t elm_size, size_t want, size_t *ha
     }
     return -1;
 }
+
+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)
+{
+	if (!pkt || !name)  {
+		return kr_error(EINVAL);
+	}
+	/* 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)];
+	knot_rdata_init(rdata_arr, rdlen, rdata, ttl);
+	knot_rdataset_add(&rr.rrs, rdata_arr, &pkt->mm);
+	/* Append RR */
+	return knot_pkt_put(pkt, 0, &rr, KNOT_PF_FREE);
+}
diff --git a/lib/utils.h b/lib/utils.h
index 963cc9a073ff55bf417e857e55f6dc6d052c79d9..a0d12db8ecfce62e464cb0a06beef3b55f4bbc93 100644
--- a/lib/utils.h
+++ b/lib/utils.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <stdio.h>
+#include <libknot/packet/pkt.h>
 
 /*
  * General-purpose attributes.
@@ -34,15 +35,19 @@ extern void _cleanup_fclose(FILE **p);
  * Defines.
  */
 
-/** @internal Fast packet reset. */
-#define KR_PKT_RECYCLE(pkt) do { \
-	(pkt)->rrset_count = 0; \
-	(pkt)->size = KNOT_WIRE_HEADER_SIZE; \
-	(pkt)->current = KNOT_ANSWER; \
-	memset((pkt)->sections, 0, sizeof((pkt)->sections)); \
-	knot_pkt_begin((pkt), KNOT_ANSWER); \
-	knot_pkt_parse_question((pkt)); \
-} while (0)
+/** Return time difference in miliseconds.
+  * @note based on the _BSD_SOURCE timersub() macro */
+static inline long time_diff(struct timeval *begin, struct timeval *end) {
+    struct timeval res = {
+        .tv_sec = end->tv_sec - begin->tv_sec,
+        .tv_usec = end->tv_usec - begin->tv_usec
+    };
+    if (res.tv_usec < 0) {
+        --res.tv_sec;
+        res.tv_usec += 1000000;
+    }
+    return res.tv_sec * 1000 + res.tv_usec / 1000;
+}
 
 /** @internal Next RDATA shortcut. */
 #define kr_rdataset_next(rd) (rd + knot_rdata_array_size(knot_rdata_rdlen(rd)))
@@ -58,3 +63,17 @@ 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);
+
+/** @internal Fast packet reset. */
+#define KR_PKT_RECYCLE(pkt) do { \
+	(pkt)->rrset_count = 0; \
+	(pkt)->size = KNOT_WIRE_HEADER_SIZE; \
+	(pkt)->current = KNOT_ANSWER; \
+	memset((pkt)->sections, 0, sizeof((pkt)->sections)); \
+	knot_pkt_begin((pkt), KNOT_ANSWER); \
+	knot_pkt_parse_question((pkt)); \
+} while (0)
+
+/** Construct and put record to packet. */
+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);
diff --git a/modules/block/README.rst b/modules/block/README.rst
index cd95296c1f42b7a90e210b8030bccc77a597d235..0802ba38a6fc14970dc77f14074bf45b879ab03b 100644
--- a/modules/block/README.rst
+++ b/modules/block/README.rst
@@ -21,6 +21,8 @@ There are three action:
 * ``DENY`` - return NXDOMAIN answer
 * ``DROP`` - terminate query resolution, returns SERVFAIL to requestor
 
+.. note:: The module (and ``kres``) treats domain names as wire, not textual representation. So each label in name is prefixed with its length, e.g. "example.com" equals to "\7example\3com".
+
 Example configuration
 ^^^^^^^^^^^^^^^^^^^^^
 
@@ -29,18 +31,18 @@ Example configuration
 	-- Load default block rules
 	modules = { 'block' }
 	-- Whitelist 'www[0-9].badboy.cz'
-	block:add(block.pattern(block.PASS, 'www[0-9].badboy.cz'))
+	block:add(block.pattern(block.PASS, '\4www[0-9]\6badboy\2cz'))
 	-- Block all names below badboy.cz
-	block:add(block.suffix(block.DENY, {'badboy.cz'}))
+	block:add(block.suffix(block.DENY, {'\6badboy\2cz'}))
 	-- Custom rule
-	block:add(function (pkt, qname)
-		if qname:find('%d.%d.%d.224.in-addr.arpa.') then
-			return block.DENY, '224.in-addr.arpa.'
+	block:add(function (req, query)
+		if query:qname():find('%d.%d.%d.224\7in-addr\4arpa') then
+			return block.DENY
 		end
 	end)
 	-- Disallow ANY queries
-	block:add(function (pkt, qname)
-		if pkt:qtype() == kres.rrtype.ANY then
+	block:add(function (req, query)
+		if query.type == kres.type.ANY then
 			return block.DROP
 		end
 	end)
@@ -51,11 +53,10 @@ Properties
 .. envvar:: block.PASS (number)
 .. envvar:: block.DENY (number)
 .. envvar:: block.DROP (number)
-.. envvar:: block.private_zones (table of private zones)
 
 .. function:: block:add(rule)
 
-  :param rule: added rule, i.e. ``block.pattern(block.DENY, '[0-9]+.cz')``
+  :param rule: added rule, i.e. ``block.pattern(block.DENY, '[0-9]+\2cz')``
   :param pattern: regular expression
   
   Policy to block queries based on the QNAME regex matching.
@@ -81,8 +82,7 @@ Properties
   :param common_suffix: common suffix of entries in suffix_table
   
   Like suffix match, but you can also provide a common suffix of all matches for faster processing (nil otherwise).
-
-.. tip:: If you want to match suffixes only, prefix the strings with `.`, e.g. `.127.in-addr.arpa.` instead of `127.in-addr.arpa`.
+  This function is faster for small suffix tables (in the order of "hundreds").
 
 .. _`Aho-Corasick`: https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_string_matching_algorithm
 .. _`@jgrahamc`: https://github.com/jgrahamc/aho-corasick-lua
diff --git a/modules/block/block.lua b/modules/block/block.lua
index 551dd436d7d8a146884efc90d8e01026745e8b1b..399b1af4d12746c8092a873b3062ab6ebf62f0c6 100644
--- a/modules/block/block.lua
+++ b/modules/block/block.lua
@@ -1,57 +1,19 @@
+local kres = require('kres')
 local block = {
 	-- Policies
 	PASS = 1, DENY = 2, DROP = 3,
 	-- Special values
 	ANY = 0,
-	-- Private, local, broadcast, test and special zones 
-	private_zones = {
-		-- RFC1918
-		'.10.in-addr.arpa.',
-		'.16.172.in-addr.arpa.',
-		'.17.172.in-addr.arpa.',
-		'.18.172.in-addr.arpa.',
-		'.19.172.in-addr.arpa.',
-		'.20.172.in-addr.arpa.',
-		'.21.172.in-addr.arpa.',
-		'.22.172.in-addr.arpa.',
-		'.23.172.in-addr.arpa.',
-		'.24.172.in-addr.arpa.',
-		'.25.172.in-addr.arpa.',
-		'.26.172.in-addr.arpa.',
-		'.27.172.in-addr.arpa.',
-		'.28.172.in-addr.arpa.',
-		'.29.172.in-addr.arpa.',
-		'.30.172.in-addr.arpa.',
-		'.31.172.in-addr.arpa.',
-		'.168.192.in-addr.arpa.',
-		-- RFC5735, RFC5737
-		'.0.in-addr.arpa.',
-		'.127.in-addr.arpa.',
-		'.254.169.in-addr.arpa.',
-		'.2.0.192.in-addr.arpa.',
-		'.100.51.198.in-addr.arpa.',
-		'.113.0.203.in-addr.arpa.',
-		'255.255.255.255.in-addr.arpa.',
-		-- IPv6 local, example
-		'0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.',
-		'1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.',
-		'.d.f.ip6.arpa.',
-		'.8.e.f.ip6.arpa.',
-		'.9.e.f.ip6.arpa.',
-		'.a.e.f.ip6.arpa.',
-		'.b.e.f.ip6.arpa.',
-		'.8.b.d.0.1.0.0.2.ip6.arpa',
-	}
 }
 
 -- @function Block requests which QNAME matches given zone list (i.e. suffix match)
 function block.suffix(action, zone_list)
 	local AC = require('aho-corasick')
 	local tree = AC.build(zone_list)
-	return function(pkt, qname)
-		local match = AC.match(tree, qname, false)
+	return function(req, query)
+		local match = AC.match(tree, query:name(), false)
 		if match[1] ~= nil then
-			return action, match[1]
+			return action
 		end
 		return nil
 	end
@@ -59,19 +21,19 @@ end
 
 -- @function Check for common suffix first, then suffix match (specialized version of suffix match)
 function block.suffix_common(action, suffix_list, common_suffix)
-	return function(pkt, qname)
+	local common_len = string.len(common_suffix)
+	local suffix_count = #suffix_list
+	return function(req, query)
 		-- Preliminary check
-		if common_suffix ~= nil then
-			local common_len = common_suffix:len()
-			if qname:sub(-common_len) ~= common_suffix then
-				return nil
-			end
+		local qname = query:name()
+		if not string.find(qname, common_suffix, -common_len, true) then
+			return nil
 		end
 		-- String match
-		for i = 1, #suffix_list do
+		for i = 1, suffix_count do
 			local zone = suffix_list[i]
-			if qname:sub(-zone:len()) == zone then
-				return action, zone
+			if string.find(qname, zone, -string.len(zone), true) then
+				return action
 			end
 		end
 		return nil
@@ -80,60 +42,100 @@ end
 
 -- @function Block QNAME pattern
 function block.pattern(action, pattern)
-	return function(pkt, qname)
-		if string.find(qname, pattern) then
-			return action, qname
+	return function(req, query)
+		if string.find(query:name(), pattern) then
+			return action
 		end
 		return nil
 	end
 end
 
 -- @function Evaluate packet in given rules to determine block action
-function block.evaluate(block, pkt, qname)
+function block.evaluate(block, req, query)
 	for i = 1, #block.rules do
-		local action, authority = block.rules[i](pkt, qname)
+		local action = block.rules[i](req, query)
 		if action ~= nil then
-			return action, authority
+			return action
 		end
 	end
-	return block.PASS, nil
+	return block.PASS
 end
 
 -- @function Block layer implementation
 block.layer = {
-	produce = function(state, req, pkt)
-		-- Interpret packet in Lua and evaluate
-		local qry = kres.query_current(req)
-		local qname = kres.query.qname(qry)
-		local action, authority = block:evaluate(pkt, qname)
+	begin = function(state, req)
+		req = kres.request_t(req)
+		local action = block:evaluate(req, req:current())
 		if action == block.DENY then
-			-- Answer full question
-			local qclass = kres.query.qclass(qry)
-			local qtype = kres.query.qtype(qry)
-			kres.query.flag(qry, kres.query.NO_MINIMIZE + kres.query.SAFEMODE)
-			pkt:question(qname, qtype, qclass)
-			pkt:flag(kres.wire.QR)
-			pkt:flag(kres.wire.AA)
 			-- Write authority information
-			pkt:rcode(kres.rcode.NXDOMAIN)
-			pkt:begin(kres.AUTHORITY)
-			pkt:add(authority, qclass, kres.type.SOA, 900,
+			local answer = req.answer
+			answer:rcode(kres.rcode.NXDOMAIN)
+			answer:begin(kres.section.AUTHORITY)
+			answer:put('\5block', 900, answer:qclass(), kres.type.SOA,
 				'\5block\0\0\0\0\0\0\0\0\14\16\0\0\3\132\0\9\58\128\0\0\3\132')
 			return kres.DONE
 		elseif action == block.DROP then
 			return kres.FAIL
-		else
-			return state
 		end
+		return state
 	end
 }
 
--- @var Default rules
-block.rules = { block.suffix_common(block.DENY, block.private_zones, '.arpa.') }
-
 -- @function Add rule to block list
 function block.add(block, rule)
 	return table.insert(block.rules, rule)
 end
 
+-- @function Convert list of string names to domain names
+function block.to_domains(names)
+	for i, v in ipairs(names) do
+		names[i] = v:gsub('([^.]*%.)', function (x)
+			return string.format('%s%s', string.char(x:len()-1), x:sub(1,-2))
+		end)
+	end
+end
+
+-- RFC1918 Private, local, broadcast, test and special zones 
+local private_zones = {
+	'10.in-addr.arpa.',
+	'16.172.in-addr.arpa.',
+	'17.172.in-addr.arpa.',
+	'18.172.in-addr.arpa.',
+	'19.172.in-addr.arpa.',
+	'20.172.in-addr.arpa.',
+	'21.172.in-addr.arpa.',
+	'22.172.in-addr.arpa.',
+	'23.172.in-addr.arpa.',
+	'24.172.in-addr.arpa.',
+	'25.172.in-addr.arpa.',
+	'26.172.in-addr.arpa.',
+	'27.172.in-addr.arpa.',
+	'28.172.in-addr.arpa.',
+	'29.172.in-addr.arpa.',
+	'30.172.in-addr.arpa.',
+	'31.172.in-addr.arpa.',
+	'168.192.in-addr.arpa.',
+	-- RFC5735, RFC5737
+	'0.in-addr.arpa.',
+	'127.in-addr.arpa.',
+	'254.169.in-addr.arpa.',
+	'2.0.192.in-addr.arpa.',
+	'100.51.198.in-addr.arpa.',
+	'113.0.203.in-addr.arpa.',
+	'255.255.255.255.in-addr.arpa.',
+	-- IPv6 local, example
+	'0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.',
+	'1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.',
+	'd.f.ip6.arpa.',
+	'8.e.f.ip6.arpa.',
+	'9.e.f.ip6.arpa.',
+	'a.e.f.ip6.arpa.',
+	'b.e.f.ip6.arpa.',
+	'8.b.d.0.1.0.0.2.ip6.arpa',
+}
+block.to_domains(private_zones)
+
+-- @var Default rules
+block.rules = { block.suffix_common(block.DENY, private_zones, '\4arpa') }
+
 return block
diff --git a/modules/stats/stats.c b/modules/stats/stats.c
index 81263984c970c33976e2a127ad1eeebf743beaa5..7941bb5f0a6107602c2d078a82dc3f316e295bf4 100644
--- a/modules/stats/stats.c
+++ b/modules/stats/stats.c
@@ -75,26 +75,12 @@ struct stat_data {
 	} queries;
 };
 
-/** @internal Subtract time (best effort) */
-float time_diff(struct timeval *begin, struct timeval *end)
-{
-	return (end->tv_sec - begin->tv_sec) * 1000 +
-	       (end->tv_usec - begin->tv_usec) / 1000.0;
-
-}
-
 /** @internal Add to const map counter */
 static inline void stat_const_add(struct stat_data *data, enum const_metric key, ssize_t incr)
 {
 	const_metrics[key].val += incr;
 }
 
-static int begin(knot_layer_t *ctx, void *module_param)
-{
-	ctx->data = module_param;
-	return ctx->state;
-}
-
 static int collect_answer(struct stat_data *data, knot_pkt_t *pkt)
 {
 	stat_const_add(data, metric_answer_total, 1);
@@ -160,14 +146,14 @@ static int collect(knot_layer_t *ctx)
 		struct kr_query *last = TAIL(rplan->resolved);
 		struct timeval now;
 		gettimeofday(&now, NULL);
-		float elapsed = time_diff(&first->timestamp, &now);
+		long elapsed = time_diff(&first->timestamp, &now);
 		if (last->flags & QUERY_CACHED) {
 			stat_const_add(data, metric_answer_cached, 1);
-		} else if (elapsed < 10.0) {
+		} else if (elapsed <= 10) {
 			stat_const_add(data, metric_answer_10ms, 1);
-		} else if (elapsed < 100.0) {
+		} else if (elapsed <= 100) {
 			stat_const_add(data, metric_answer_100ms, 1);
-		} else if (elapsed < 1000.0) {
+		} else if (elapsed <= 1000) {
 			stat_const_add(data, metric_answer_1000ms, 1);
 		} else {
 			stat_const_add(data, metric_answer_slow, 1);
@@ -341,7 +327,6 @@ static char* clear_expiring(void *env, struct kr_module *module, const char *arg
 const knot_layer_api_t *stats_layer(struct kr_module *module)
 {
 	static knot_layer_api_t _layer = {
-		.begin = &begin,
 		.finish = &collect,
 	};
 	/* Store module reference */
diff --git a/platform.mk b/platform.mk
index 39712ed7aa84dd57d068b8f90b3fa553003fc076..1918074dca0d684f398d6b1634fcd5346f517c7d 100644
--- a/platform.mk
+++ b/platform.mk
@@ -24,7 +24,7 @@ else
         MODTYPE := dynamiclib
     else
         PLATFORM := POSIX
-        LDFLAGS += -pthread
+        LDFLAGS += -pthread -lm -Wl,-E
         ifeq (,$(findstring BSD,$(UNAME)))
             LDFLAGS += -ldl
         endif
diff --git a/scripts/bootstrap-depends.sh b/scripts/bootstrap-depends.sh
index 6e32fe1506d6f20abc1189312bbd3fd769caf277..b8e4b22b2d78cad40de59c04fb8c930715bd375b 100755
--- a/scripts/bootstrap-depends.sh
+++ b/scripts/bootstrap-depends.sh
@@ -15,8 +15,8 @@ NETTLE_TAG="2.7.1"
 NETTLE_URL="https://ftp.gnu.org/gnu/nettle/nettle-${NETTLE_TAG}.tar.gz"
 GNUTLS_TAG="3.3.12"
 GNUTLS_URL="ftp://ftp.gnutls.org/gcrypt/gnutls/v3.3/gnutls-${GNUTLS_TAG}.tar.xz"
-LUA_TAG="5.2.3"
-LUA_URL="http://www.lua.org/ftp/lua-${LUA_TAG}.tar.gz"
+LUA_TAG="v2.1"
+LUA_URL="http://luajit.org/git/luajit-2.0.git"
 
 # prepare install prefix
 PREFIX=${1}; [ -z ${PREFIX} ] && export PREFIX="${HOME}/.local"
@@ -108,23 +108,8 @@ pkg libknot ${KNOT_URL} ${KNOT_TAG} include/libknot \
 pkg cmocka ${CMOCKA_URL} ${CMOCKA_TAG} include/cmocka.h
 # libuv
 pkg libuv ${LIBUV_URL} ${LIBUV_TAG} include/uv.h --disable-static
-# lua
-pkg lua ${LUA_URL} ${LUA_TAG} include/lua.h posix install INSTALL_TOP=${PREFIX} MYCFLAGS="-DLUA_USE_DLOPEN" MYLIBS="-ldl"
-if [ ! -f ${PREFIX}/lib/pkgconfig/lua.pc ]; then
-cat > ${PREFIX}/lib/pkgconfig/lua.pc << EOF
-prefix=${PREFIX}
-exec_prefix=\${prefix}
-libdir=\${exec_prefix}/lib
-includedir=\${prefix}/include
-
-Name: Lua
-Description: An Extensible Extension Language
-Version: ${LUA_TAG}
-Requires:
-Libs: -L\${libdir} -llua -lm
-Cflags: -I\${includedir}
-EOF
-fi
+# luajit
+pkg lua ${LUA_URL} ${LUA_TAG} include/lua.h install LDFLAGS=-lm PREFIX=${PREFIX}
 
 # remove on successful build
 rm -rf ${BUILD_DIR}