diff --git a/daemon/bindings/kres.c b/daemon/bindings/kres.c
index f9d11c1625a4a9be9acea3f7071cb036f314e042..8ca2600295c2060521f050480299e331d373c048 100644
--- a/daemon/bindings/kres.c
+++ b/daemon/bindings/kres.c
@@ -17,13 +17,37 @@
 #include "daemon/bindings/kres.h"
 #include "daemon/bindings.h"
 
+/** @internal Create userdata of given type. */
+#define UDATA_CREATE(L, type, val, meta) do { \
+	type *udata = lua_newuserdata(L, sizeof(*udata)); \
+	*udata = (val); \
+	luaL_getmetatable(L, (meta)); \
+	lua_setmetatable(L, -2); \
+} while (0)
+
+#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))
+
 /** @internal Register metatable. */
-#define META_REGISTER(L, funcs, name) \
-	luaL_newmetatable((L), (name)); \
-	luaL_setfuncs((L), (funcs), 0); \
-	lua_pushvalue((L), -1); \
-	lua_setfield((L), -2, "__index"); \
-	lua_pop((L), 1);
+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)
@@ -35,6 +59,7 @@ static inline void lua_pushdname(lua_State *L, const knot_dname_t *name)
 
 /* 
  * Packet interface
+ * @note Packets are always light userdata, use single pointers.
  */
 
 #define WIRE_FLAGS(X) \
@@ -95,17 +120,68 @@ static int pkt_qclass(lua_State *L)
 {
 	knot_pkt_t *pkt = lua_touserdata(L, 1);
 	lua_pushnumber(L, knot_pkt_qclass(pkt));
-	return 1;	
+	return 1;
 }
 
 static int pkt_qname(lua_State *L)
 {
 	knot_pkt_t *pkt = lua_touserdata(L, 1);
 	lua_pushdname(L, knot_pkt_qname(pkt));
-	return 1;	
+	return 1;
+}
+
+static int pkt_question(lua_State *L)
+{
+	knot_pkt_t *pkt = lua_touserdata(L, 1);
+	if (lua_gettop(L) < 4) {
+		return 0;
+	}
+	uint8_t dname[KNOT_DNAME_MAXLEN];
+	knot_dname_from_str(dname, lua_tostring(L, 2), sizeof(dname));
+	if (!knot_dname_is_equal(knot_pkt_qname(pkt), dname)) {
+		uint8_t header[KNOT_WIRE_HEADER_SIZE];
+		memcpy(header, pkt->wire, sizeof(header));
+		knot_pkt_clear(pkt);
+		memcpy(pkt->wire, header, sizeof(header));
+		size_t max_size = pkt->max_size;
+		knot_pkt_put_question(pkt, dname, lua_tointeger(L, 3), lua_tointeger(L, 4));
+		// pkt->parsed = pkt->size;
+		pkt->max_size = max_size;
+	}
+	return 0;
 }
 
-#warning TODO: record interfaces
+static int pkt_begin(lua_State *L)
+{
+	knot_pkt_t *pkt = lua_touserdata(L, 1);
+	knot_pkt_begin(pkt, lua_tointeger(L, 2));
+	return 0;
+}
+
+static int pkt_add(lua_State *L)
+{
+	knot_pkt_t *pkt = lua_touserdata(L, 1);
+	if (lua_gettop(L) < 6) {
+		return 0;
+	}
+	/* Create empty RR */
+	uint8_t dname[KNOT_DNAME_MAXLEN];
+	knot_dname_from_str(dname, lua_tostring(L, 2), sizeof(dname));
+	knot_rrset_t rr;
+	knot_rrset_init(&rr, knot_dname_copy(dname, &pkt->mm), lua_tointeger(L, 3), lua_tointeger(L, 4));
+	/* Create RDATA */
+	uint32_t ttl = lua_tointeger(L, 5);
+	size_t rdlen = 0;
+	const char *raw_data = lua_tolstring(L, 6, &rdlen);
+	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_meta_register(lua_State *L)
 {
@@ -116,93 +192,97 @@ static int pkt_meta_register(lua_State *L)
 		{ "qtype",     pkt_qtype  },
 		{ "qclass",    pkt_qclass },
 		{ "qname",     pkt_qname  },
+		{ "question",  pkt_question },
+		{ "begin",     pkt_begin },
+		{ "add",       pkt_add },
 		{ NULL, NULL }
 	};
-	META_REGISTER (L, wrap, META_PKT);
+	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 = lua_touserdata(L, 1);
-	lua_pushnumber(L, qry->stype);
+	struct kr_query **qry = lua_touserdata(L, 1);
+	lua_pushnumber(L, (*qry)->stype);
 	return 1;
 }
 
 static int query_qclass(lua_State *L)
 {
-	struct kr_query *qry = lua_touserdata(L, 1);
-	lua_pushnumber(L, qry->sclass);
+	struct kr_query **qry = lua_touserdata(L, 1);
+	lua_pushnumber(L, (*qry)->sclass);
 	return 1;	
 }
 
 static int query_qname(lua_State *L)
 {
-	struct kr_query *qry = lua_touserdata(L, 1);
-	lua_pushdname(L, qry->sname);
+	struct kr_query **qry = lua_touserdata(L, 1);
+	lua_pushdname(L, (*qry)->sname);
 	return 1;	
 }
 
-static int query_meta_register(lua_State *L)
+static int query_flag(lua_State *L)
 {
-	static const luaL_Reg wrap[] = {
-		{ "qtype",     query_qtype  },
-		{ "qclass",    query_qclass },
-		{ "qname",     query_qname  },
-		{ NULL, NULL }
-	};
-	META_REGISTER (L, wrap, META_QUERY);
+	struct kr_query **qry = lua_touserdata(L, 1);
+	if (lua_gettop(L) < 2 || !lua_isnumber(L, 2)) {
+		return 0;
+	}
+	(*qry)->flags |= lua_tointeger(L, 2);
 	return 0;
 }
 
-/**
- * Resolution context interface.
- */
+static int query_clear_flag(lua_State *L)
+{
+	struct kr_query **qry = lua_touserdata(L, 1);
+	if (lua_gettop(L) < 2 || !lua_isnumber(L, 2)) {
+		return 0;
+	}
+	(*qry)->flags &= ~lua_tointeger(L, 2);
+	return 0;
+}
 
-static int rplan_query(lua_State *L)
+static int query_has_flag(lua_State *L)
 {
-	struct kr_rplan *rplan = lua_touserdata(L, 1);
-	lua_pushlightuserdata(L, kr_rplan_current(rplan));
-	luaL_getmetatable(L, META_QUERY);
-	lua_setmetatable(L, -2);
+	struct kr_query **qry = lua_touserdata(L, 1);
+	if (lua_gettop(L) < 2 || !lua_isnumber(L, 2)) {
+		return 0;
+	}
+	lua_pushboolean(L, (*qry)->flags & lua_tointeger(L, 2));
 	return 1;
 }
 
-static int rplan_meta_register(lua_State *L)
+static int query_meta_register(lua_State *L)
 {
 	static const luaL_Reg wrap[] = {
-		{ "query",      rplan_query },
-		// { "pending",    rplan_pending },
-		// { "resolved",   rplan_resolved },
+		{ "qtype",     query_qtype  },
+		{ "qclass",    query_qclass },
+		{ "qname",     query_qname  },
+		{ "flag",      query_flag   },
+		{ "clear_flag",  query_clear_flag },
+		{ "has_flag",  query_has_flag },
 		{ NULL, NULL }
 	};
-	META_REGISTER (L, wrap, META_RPLAN);
+	lua_register_meta (L, wrap, META_QUERY);
 	return 0;
 }
 
-#warning TODO: context interface, rplan
-
-#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 (lookup_table_t *elm = (table); elm->name; ++elm) { \
-		WRAP_NUMBER((L), elm->name, elm->id); \
-	} \
-	lua_setfield((L), -2, (prefix))
+static int query_current(lua_State *L)
+{
+	struct kr_request *req = lua_touserdata(L, 1);
+	UDATA_CREATE(L, struct kr_query *, kr_rplan_current(&req->rplan), META_QUERY);
+	return 1;
+}
 
 int lib_kres(lua_State *L)
 {
 	static const luaL_Reg lib[] = {
+		{ "query_current", query_current },
 		{ NULL, NULL }
 	};
 	/* Create module and register functions */
@@ -213,13 +293,17 @@ int lib_kres(lua_State *L)
 	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, "opcode", knot_opcode_names);
 	WRAP_LUT(L, "wire",   wire_flag_names);
+	WRAP_LUT(L, "query",  query_flag_names);
 	/* Register metatables */
 	pkt_meta_register(L);
 	query_meta_register(L);
-	rplan_meta_register(L);
 	return 1;	
 }
\ No newline at end of file
diff --git a/daemon/ffimodule.c b/daemon/ffimodule.c
index 26eed9674097a4422581dbfa44e27c4f1536366d..c920ffb22c221581201ee7e761147ed352a4a245 100644
--- a/daemon/ffimodule.c
+++ b/daemon/ffimodule.c
@@ -141,13 +141,6 @@ static int l_ffi_deinit(struct kr_module *module)
 	} \
 	lua_pushnumber(L, ctx->state)
 
-/** @internal Push rplan and metatable. */
-#define LAYER_PUSH_RPLAN(ctx) do { \
-	struct kr_request *req = (ctx)->data; \
-	lua_pushlightuserdata(L, &req->rplan); \
-	set_metatable(L, META_RPLAN); \
-} while (0)
-
 static int l_ffi_layer_begin(knot_layer_t *ctx, void *module_param)
 {
 	ctx->data = module_param;
@@ -159,21 +152,21 @@ static int l_ffi_layer_begin(knot_layer_t *ctx, void *module_param)
 static int l_ffi_layer_reset(knot_layer_t *ctx)
 {
 	LAYER_FFI_CALL(ctx, "reset");
-	LAYER_PUSH_RPLAN(ctx);
+	lua_pushlightuserdata(L, ctx->data);
 	return l_ffi_call(L, 2);
 }
 
 static int l_ffi_layer_finish(knot_layer_t *ctx)
 {
 	LAYER_FFI_CALL(ctx, "finish");
-	LAYER_PUSH_RPLAN(ctx);
+	lua_pushlightuserdata(L, ctx->data);
 	return l_ffi_call(L, 2);
 }
 
 static int l_ffi_layer_consume(knot_layer_t *ctx, knot_pkt_t *pkt)
 {
 	LAYER_FFI_CALL(ctx, "consume");
-	LAYER_PUSH_RPLAN(ctx);
+	lua_pushlightuserdata(L, ctx->data);
 	lua_pushlightuserdata(L, pkt);
 	set_metatable(L, META_PKT);
 	return l_ffi_call(L, 3);
@@ -182,7 +175,7 @@ static int l_ffi_layer_consume(knot_layer_t *ctx, knot_pkt_t *pkt)
 static int l_ffi_layer_produce(knot_layer_t *ctx, knot_pkt_t *pkt)
 {
 	LAYER_FFI_CALL(ctx, "produce");
-	LAYER_PUSH_RPLAN(ctx);
+	lua_pushlightuserdata(L, ctx->data);
 	lua_pushlightuserdata(L, pkt);
 	set_metatable(L, META_PKT);
 	return l_ffi_call(L, 3);
@@ -191,7 +184,7 @@ static int l_ffi_layer_produce(knot_layer_t *ctx, knot_pkt_t *pkt)
 static int l_ffi_layer_fail(knot_layer_t *ctx, knot_pkt_t *pkt)
 {
 	LAYER_FFI_CALL(ctx, "fail");
-	LAYER_PUSH_RPLAN(ctx);
+	lua_pushlightuserdata(L, ctx->data);
 	lua_pushlightuserdata(L, pkt);
 	set_metatable(L, META_PKT);
 	return l_ffi_call(L, 3);
@@ -237,7 +230,6 @@ static const knot_layer_api_t* l_ffi_layer(struct kr_module *module)
 
 #undef LAYER_REGISTER
 #undef LAYER_FFI_CALL
-#undef LAYER_PUSH_RPLAN
 
 /** @internal Helper macro for function presence check. */
 #define REGISTER_FFI_CALL(L, attr, name, cb) do { \
diff --git a/modules/block/block.lua b/modules/block/block.lua
index facd35f3377bfe49b276b2f70278965ed91153d9..39e9470a288f8c2525d973c397200da09d4c1e58 100644
--- a/modules/block/block.lua
+++ b/modules/block/block.lua
@@ -47,7 +47,7 @@ local block = {
 -- @function Block requests which QNAME matches given zone list
 function block.in_zone(zone_list)
 	return function(pkt, qry)
-		local qname = pkt:qname()
+		local qname = qry:qname()
 		for _,zone in pairs(zone_list) do
 			if qname:sub(-zone:len()) == zone then
 				return block.DENY
@@ -70,21 +70,25 @@ end
 
 -- @function Block layer implementation
 block.layer = {
-	produce = function(state, data, pkt)
+	produce = function(state, req, pkt)
 		-- Only when a query isn't already answered
 		if state ~= kres.CONSUME then
 			return state
 		end
-		-- @todo Interpret QUERY (as it has final name)
 		-- Interpret packet in Lua and evaluate
-		local pkt = kres.packet(pkt)
-		local action = block:evaluate(pkt, nil)
+		local qry = kres.query_current(req)
+		local action = block:evaluate(pkt, qry)
 		if action == block.DENY then
+			-- Answer full question
+			qry:flag(kres.query.NO_MINIMIZE)
+			pkt:question(qry:qname(), qry:qclass(), qry:qtype())
 			pkt:flag(kres.wire.QR)
 			pkt:flag(kres.wire.AA)
-			pkt:flag(kres.wire.CD)
+			-- Write authority information
 			pkt:rcode(kres.rcode.NXDOMAIN)
-			-- @todo add SOA record
+			pkt:begin(kres.AUTHORITY)
+			-- pkt:add(qry:qname(), qry:qclass(), 6, 900,
+			-- 	'abcd\0efgh\0'..'\0\0\0\1'..'\0\0\0\0'..'\132\3\0\0'..'\132\3\0\0'..'\132\3\0\0')
 			return kres.DONE
 		elseif action == block.DROP then
 			return kres.FAIL