diff --git a/Knot.files b/Knot.files
index ed4d228bb68b8c5002818a807db9e1a2cec39491..e5a0c40456815bd616505c48b0841f03d4ed0968 100644
--- a/Knot.files
+++ b/Knot.files
@@ -166,6 +166,8 @@ src/libknot/edns.h
 src/libknot/libknot.h
 src/libknot/nameserver/chaos.c
 src/libknot/nameserver/chaos.h
+src/libknot/nameserver/internet.c
+src/libknot/nameserver/internet.h
 src/libknot/nameserver/name-server.c
 src/libknot/nameserver/name-server.h
 src/libknot/nameserver/ns_proc_query.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 610e3e19fdd5ff03adc8148a7ed12d442864285d..48f1b177a6412ab60a6afdc3bc53554ed654a581 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -122,6 +122,8 @@ libknot_la_SOURCES =				\
 	libknot/nameserver/name-server.c	\
 	libknot/nameserver/chaos.h		\
 	libknot/nameserver/chaos.c		\
+	libknot/nameserver/internet.h	\
+	libknot/nameserver/internet.c	\
 	libknot/nameserver/ns_proc_query.h	\
 	libknot/nameserver/ns_proc_query.c	\
 	libknot/updates/changesets.h		\
diff --git a/src/libknot/nameserver/name-server.h b/src/libknot/nameserver/name-server.h
index 4b114e311a8d7de40e69625d63a7da7fbc53ec53..db498369330afe840743e035aa6a45f465a4b8f1 100644
--- a/src/libknot/nameserver/name-server.h
+++ b/src/libknot/nameserver/name-server.h
@@ -447,12 +447,18 @@ enum ns_proc_state {
 	NS_PROC_FAIL = 1 << 3,
 };
 
+enum ns_proc_flag {
+	NS_NOFLAG          = 0,
+	NS_PKTSIZE_NOLIMIT = 1 << 0, /* Don't limit packet size (for TCP). */
+};
+
 struct ns_proc_module;
 
 typedef struct ns_proc_context
 {
 	mm_ctx_t mm;
 	uint16_t type;
+	uint16_t flags;
 	void *data;
 
 	int state;
diff --git a/src/libknot/nameserver/ns_proc_query.c b/src/libknot/nameserver/ns_proc_query.c
index 1d79663f3c5aee995c5c29a008022dfb7ecd43c1..83807f8876b89fb83591b4537ae27b13975dfc36 100644
--- a/src/libknot/nameserver/ns_proc_query.c
+++ b/src/libknot/nameserver/ns_proc_query.c
@@ -3,30 +3,19 @@
 #include <urcu.h>
 
 #include "libknot/nameserver/ns_proc_query.h"
-#include "common/descriptor.h"
-#include "libknot/common.h"
 #include "libknot/consts.h"
-#include "libknot/rdata.h"
 #include "libknot/util/debug.h"
 #include "libknot/nameserver/chaos.h"
-
-struct query_data {
-	uint16_t rcode;
-	uint16_t rcode_tsig;
-	knot_pkt_t *pkt;
-	const knot_node_t *node, *encloser, *previous;
-	list_t wildcards;
-	mm_ctx_t *mm;
-};
+#include "libknot/nameserver/internet.h"
+#include "libknot/common.h"
+#include "common/descriptor.h"
 
 /* Forward decls. */
-int answer_internet(knot_pkt_t *pkt, ns_proc_context_t *ctx);
-int answer_chaos(knot_pkt_t *pkt, ns_proc_context_t *ctx);
 static int tsig_check(knot_pkt_t *pkt);
-static int zone_state(const knot_zone_t *zone);
-static const knot_zone_t *find_query_zone(knot_pkt_t *pkt, knot_nameserver_t *ns);
-static int prepare_answer(knot_pkt_t *query, knot_pkt_t *resp, knot_nameserver_t *ns);
-static int in_zone_answer(knot_pkt_t *resp, struct query_data *qdata);
+static const knot_zone_t *answer_zone_find(knot_pkt_t *pkt, knot_zonedb_t *zonedb);
+static int prepare_answer(knot_pkt_t *query, knot_pkt_t *resp, ns_proc_context_t *ctx);
+int query_internet(knot_pkt_t *pkt, ns_proc_context_t *ctx);
+int query_chaos(knot_pkt_t *pkt, ns_proc_context_t *ctx);
 
 /*! \brief Module implementation. */
 const ns_proc_module_t _ns_proc_query = {
@@ -124,7 +113,7 @@ int ns_proc_query_out(knot_pkt_t *pkt, ns_proc_context_t *ctx)
 
 	/* Prepare answer. */
 	int next_state = NS_PROC_FINISH;
-	int ret = prepare_answer(data->pkt, pkt, ctx->ns);
+	int ret = prepare_answer(data->pkt, pkt, ctx);
 	if (ret != KNOT_EOK) {
 		data->rcode = KNOT_RCODE_SERVFAIL;
 		rcu_read_unlock();
@@ -136,11 +125,11 @@ int ns_proc_query_out(knot_pkt_t *pkt, ns_proc_context_t *ctx)
 	/* Answer based on qclass. */
 	switch (knot_pkt_qclass(pkt)) {
 	case KNOT_CLASS_CH:
-		next_state = answer_chaos(pkt, ctx);
+		next_state = query_chaos(pkt, ctx);
 		break;
 	case KNOT_CLASS_ANY:
 	case KNOT_CLASS_IN:
-		next_state = answer_internet(pkt, ctx);
+		next_state = query_internet(pkt, ctx);
 		break;
 	default:
 		data->rcode = KNOT_RCODE_REFUSED;
@@ -181,14 +170,14 @@ int ns_proc_query_err(knot_pkt_t *pkt, ns_proc_context_t *ctx)
 /*!
  * \brief Create a response for a given query in the CHAOS class.
  */
-int answer_internet(knot_pkt_t *pkt, ns_proc_context_t *ctx)
+int query_internet(knot_pkt_t *pkt, ns_proc_context_t *ctx)
 {
 	struct query_data *data = QUERY_DATA(ctx);
 	int next_state = NS_PROC_FAIL;
 
 	/* Check zone validity. */
-	switch(zone_state(pkt->zone)) {
-	case KNOT_EOK:     next_state = in_zone_answer(pkt, data); break;
+	switch(knot_zone_state(pkt->zone)) {
+	case KNOT_EOK:     next_state = internet_answer(pkt, data); break;
 	case KNOT_ENOENT:  data->rcode = KNOT_RCODE_REFUSED; break;
 	default:           data->rcode = KNOT_RCODE_SERVFAIL; break;
 	}
@@ -199,7 +188,7 @@ int answer_internet(knot_pkt_t *pkt, ns_proc_context_t *ctx)
 /*!
  * \brief Create a response for a given query in the CHAOS class.
  */
-int answer_chaos(knot_pkt_t *pkt, ns_proc_context_t *ctx)
+int query_chaos(knot_pkt_t *pkt, ns_proc_context_t *ctx)
 {
 	dbg_ns("%s(%p, %p)\n", __func__, pkt, ctx);
 	struct query_data *data = QUERY_DATA(ctx);
@@ -223,19 +212,7 @@ static int tsig_check(knot_pkt_t *pkt)
 	return KNOT_EOK;
 }
 
-static int zone_state(const knot_zone_t *zone)
-{
-	if (zone == NULL) {
-		dbg_ns("%s: zone not found\n", __func__);
-		return KNOT_ENOENT;
-	} else if (zone->contents == NULL) {
-		dbg_ns("%s: zone expired or stub\n", __func__);
-		return KNOT_ENOZONE;
-	}
-	return KNOT_EOK;
-}
-
-static const knot_zone_t *find_query_zone(knot_pkt_t *pkt, knot_nameserver_t *ns)
+static const knot_zone_t *answer_zone_find(knot_pkt_t *pkt, knot_zonedb_t *zonedb)
 {
 	uint16_t qtype = knot_pkt_qtype(pkt);
 	uint16_t qclass = knot_pkt_qclass(pkt);
@@ -247,13 +224,12 @@ static const knot_zone_t *find_query_zone(knot_pkt_t *pkt, knot_nameserver_t *ns
 	}
 
 	// find zone in which to search for the name
-	knot_zonedb_t *zonedb = rcu_dereference(ns->zone_db);
 	return ns_get_zone_for_qname(zonedb, qname, qtype);
 }
 
-static int prepare_answer(knot_pkt_t *query, knot_pkt_t *resp, knot_nameserver_t *ns)
+static int prepare_answer(knot_pkt_t *query, knot_pkt_t *resp, ns_proc_context_t *ctx)
 {
-	dbg_ns("%s(%p, %p, %p)\n", __func__, query, resp, ns);
+	dbg_ns("%s(%p, %p, %p)\n", __func__, query, resp, ctx);
 
 	int ret = knot_pkt_init_response(resp, query);
 	if (ret != KNOT_EOK) {
@@ -262,326 +238,36 @@ static int prepare_answer(knot_pkt_t *query, knot_pkt_t *resp, knot_nameserver_t
 	}
 
 	// find zone for qname
-	resp->zone = find_query_zone(query, ns);
-	dbg_ns("%s: found zone %p for pkt %p\n", __func__, resp->zone, query);
+	resp->zone = answer_zone_find(query, ctx->ns->zone_db);
+
+	/* Update maximal answer size. */
+	if (!(ctx->flags & NS_PKTSIZE_NOLIMIT)) {
+		resp->max_size = KNOT_WIRE_MIN_PKTSIZE;
+	}
 
 	/* Check if EDNS is supported. */
 	if (!knot_pkt_have_edns(query)) {
+		dbg_ns("%s: packet size limit %zuB\n", __func__, resp->max_size);
 		return KNOT_EOK;
 	}
-
-	// set the OPT RR to the response
-	ret = knot_pkt_add_opt(resp, ns->opt_rr, knot_pkt_have_nsid(query));
-	if (ret == KNOT_EOK) {
-		// copy the DO bit from the query
-		if (knot_pkt_have_dnssec(query)) {
-			dbg_ns("%s: setting DO=1 in OPT RR\n", __func__);
-			knot_edns_set_do(&(resp)->opt_rr);
-		}
-	} else {
-		dbg_ns("%s: can't add OPT RR (%d)\n", __func__, ret);
-	}
-
-	return ret;
-}
-
-enum {
-	BEGIN,
-	NODATA,
-	HIT,
-	MISS,
-	DELEG,
-	FOLLOW,
-	ERROR
-};
-
-int in_zone_name_cname(knot_pkt_t *pkt, const knot_dname_t **name, struct query_data *qdata)
-{
-	dbg_ns("%s(%p, %p, %p)\n", __func__, name, pkt, qdata);
-
-	const knot_node_t *cname_node = qdata->node;
-	knot_rrset_t *cname_rr = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_CNAME);
-	knot_rrset_t *rr_to_add = cname_rr;
-	unsigned flags = 0;
-	int ret = KNOT_EOK;
-
-	assert(cname_rr != NULL);
-
-	/* Is node a wildcard? */
-	if (knot_dname_is_wildcard(cname_node->owner)) {
-
-		/* Check if is not in wildcard nodes (loop). */
-		dbg_ns("%s: CNAME node %p is wildcard\n", __func__, cname_node);
-		if (ptrlist_contains(&qdata->wildcards, cname_node)) {
-			dbg_ns("%s: node %p already visited => CNAME loop\n",
-			       __func__, cname_node);
-			return HIT;
-		}
-
-		/* Put to wildcard node list. */
-		if (ptrlist_add(&qdata->wildcards, cname_node, qdata->mm) == NULL) {
-			qdata->rcode = KNOT_RCODE_SERVFAIL;
-			return ERROR;
-		}
-
-		/* Synthetic RRSet. */
-		rr_to_add = ns_synth_from_wildcard(cname_rr, *name);
-
-		/* Free RRSet with packet. */
-		flags |= KNOT_PF_FREE;
-
-	} else {
-		/* Normal CNAME name, check for duplicate. */
-		flags |= KNOT_PF_CHECKDUP;
-	}
-
-	/* Now, try to put CNAME to answer. */
-	uint16_t rr_count_before = pkt->rrset_count;
-	ret = knot_pkt_put(pkt, 0, rr_to_add, flags);
+	ret = knot_pkt_add_opt(resp, ctx->ns->opt_rr, knot_pkt_have_nsid(query));
 	if (ret != KNOT_EOK) {
-		/* Free if synthetized. */
-		if (rr_to_add != cname_rr) {
-			knot_rrset_deep_free(&rr_to_add, 1);
-		}
-		qdata->rcode = KNOT_RCODE_SERVFAIL;
-		return ERROR;
-	} else {
-		/* Check if RR count increased. */
-		if (pkt->rrset_count <= rr_count_before) {
-			dbg_ns("%s: RR %p already inserted => CNAME loop\n",
-			       __func__, rr_to_add);
-			return HIT;
-		}
-
-	}
-
-	/* Add RR signatures (from original RR). */
-	ret = ns_add_rrsigs(cname_rr, pkt, *name, 0);
-	if (ret != KNOT_EOK) {
-		dbg_ns("%s: couldn't add rrsigs for CNAME RRSet %p\n",
-		       __func__, cname_rr);
-		qdata->rcode = KNOT_RCODE_SERVFAIL;
-		return ERROR;
-	}
-
-	/* Now follow the next CNAME TARGET. */
-	*name = knot_rdata_cname_name(cname_rr);
-
-#ifdef KNOT_NS_DEBUG
-	char *cname_str = knot_dname_to_str(cname_node->owner);
-	char *target_str = knot_dname_to_str(*name);
-	dbg_ns("%s: FOLLOW '%s' -> '%s'\n", __func__, cname_str, target_str);
-	free(cname_str);
-	free(target_str);
-#endif /* KNOT_NS_DEBUG */
-
-	return FOLLOW;
-}
-
-static int in_zone_name_found(knot_pkt_t *pkt, const knot_dname_t **name,
-                              struct query_data *qdata)
-{
-	uint16_t qtype = knot_pkt_qtype(pkt);
-	dbg_ns("%s(%p, %p, %p)\n", __func__, pkt, name, qdata);
-
-	if (knot_node_rrset(qdata->node, KNOT_RRTYPE_CNAME) != NULL
-	    && qtype != KNOT_RRTYPE_CNAME
-	    && qtype != KNOT_RRTYPE_RRSIG
-	    && qtype != KNOT_RRTYPE_ANY) {
-		dbg_ns("%s: solving CNAME\n", __func__);
-		return in_zone_name_cname(pkt, name, qdata);
-	}
-
-	// now we have the node for answering
-	if (qtype != KNOT_RRTYPE_DS && // DS query is answered normally
-	    (knot_node_is_deleg_point(qdata->node) || knot_node_is_non_auth(qdata->node))) {
-		dbg_ns("%s: solving REFERRAL\n", __func__);
-		return DELEG;
-	}
-
-	int added = 0; /*! \todo useless */
-	int ret = ns_put_answer(qdata->node, pkt->zone->contents, *name, qtype, pkt, &added, 0 /*! \todo check from pkt */);
-
-	if (ret != KNOT_EOK) {
-		dbg_ns("%s: failed answer from node %p (%d)\n", __func__, qdata->node, ret);
-		/*! \todo set rcode */
-		return ERROR;
-	} else {
-		dbg_ns("%s: answered, %d added\n", __func__, added);
-	}
-
-	// this is the only case when the servers answers from
-	// particular node, i.e. the only case when it may return SOA
-	// or NS records in Answer section
-	if (knot_wire_get_tc(pkt->wire) == 0
-	    && knot_pkt_have_dnssec(pkt->query)
-	    && qdata->node == knot_zone_contents_apex(pkt->zone->contents)
-	    && (qtype == KNOT_RRTYPE_SOA || qtype == KNOT_RRTYPE_NS)) {
-		ret = ns_add_dnskey(qdata->node, pkt);
-		if (ret != KNOT_EOK) {
-			qdata->rcode = KNOT_RCODE_SERVFAIL;
-			return ERROR;
-		}
-	}
-
-	/* Check for NODATA. */
-	if (added == 0) {
-		return NODATA;
-	} else {
-		return HIT;
-	}
-}
-
-static int in_zone_name_not_found(knot_pkt_t *pkt, const knot_dname_t **name,
-                                  struct query_data *qdata)
-{
-	dbg_ns("%s(%p, %p, %p)\n", __func__, pkt, name, qdata);
-
-	/* Name is covered by wildcard. */
-	const knot_node_t *wildcard_node = knot_node_wildcard_child(qdata->encloser);
-	if (wildcard_node) {
-		dbg_ns("%s: name %p covered by wildcard\n", __func__, *name);
-		qdata->node = wildcard_node;
-		qdata->encloser = wildcard_node;
-		qdata->previous = NULL;
-		return in_zone_name_found(pkt, name, qdata);
-	}
-
-	/* Name is under DNAME, use it for substitution. */
-	knot_rrset_t *dname_rrset = knot_node_get_rrset(qdata->encloser, KNOT_RRTYPE_DNAME);
-	if (dname_rrset != NULL
-	    && knot_rrset_rdata_rr_count(dname_rrset) > 0) {
-		dbg_ns("%s: solving DNAME for name %p\n", __func__, *name);
-		int ret = ns_process_dname(dname_rrset, name, pkt);
-		if (ret != KNOT_EOK) {
-			qdata->rcode = KNOT_RCODE_SERVFAIL;
-			return ERROR;
-		}
-
-		return FOLLOW;
-	}
-
-	dbg_ns("%s: name not found in zone %p\n", __func__, *name);
-	return MISS;
-}
-
-static int in_zone_solve_name(int state, const knot_dname_t **name,
-                                knot_pkt_t *pkt, struct query_data *qdata)
-{
-	dbg_ns("%s(%d, %p, %p, %p)\n", __func__, state, name, pkt, qdata);
-	int ret = knot_zone_contents_find_dname(pkt->zone->contents, *name,
-	                                        &qdata->node, &qdata->encloser,
-	                                        &qdata->previous);
-
-	switch(ret) {
-	case KNOT_ZONE_NAME_FOUND:
-		return in_zone_name_found(pkt, name, qdata);
-	case KNOT_ZONE_NAME_NOT_FOUND:
-		return in_zone_name_not_found(pkt, name, qdata);
-	case KNOT_EOUTOFZONE:
-		assert(state == FOLLOW); /* CNAME/DNAME chain only. */
-		return HIT;
-	default:
-		return ERROR;
+		dbg_ns("%s: can't add OPT RR (%d)\n", __func__, ret);
+		return ret;
 	}
-}
-
-static int in_zone_solve_answer(const knot_dname_t **qname,
-                                    knot_pkt_t *pkt, struct query_data *qdata)
-{
-	/* Get answer to QNAME. */
-	int state = in_zone_solve_name(BEGIN, qname, pkt, qdata);
 
-	/* Is authoritative answer unless referral.
-	 * Must check before we chase the CNAME chain. */
-	if (state != DELEG) {
-		knot_wire_set_aa(pkt->wire);
+	/* Copy DO bit if set (DNSSEC requested). */
+	if (knot_pkt_have_dnssec(query)) {
+		dbg_ns("%s: setting DO=1 in OPT RR\n", __func__);
+		knot_edns_set_do(&(resp)->opt_rr);
 	}
-
-	/* Additional resolving for CNAME/DNAME chain. */
-	while (state == FOLLOW) {
-		state = in_zone_solve_name(state, qname, pkt, qdata);
-		/* Chain lead to NXDOMAIN, this is okay since
-		 * the first CNAME/DNAME is a valid answer. */
-		if (state == MISS) {
-			state = HIT;
-		}
-	}
-
-	return state;
-}
-
-static int in_zone_solve_authority(int state, const knot_dname_t **qname,
-                                   knot_pkt_t *pkt, struct query_data *qdata)
-{
-	int ret = KNOT_ERROR;
-
-	switch (state) {
-	case HIT:    /* Positive response, add (optional) AUTHORITY NS. */
-		ret = ns_put_authority_ns(pkt->zone->contents, pkt);
-		dbg_ns("%s: putting authority NS = %d\n", __func__, ret);
-		break;
-	case MISS:   /* MISS, set NXDOMAIN RCODE. */
-		qdata->rcode = KNOT_RCODE_NXDOMAIN;
-		dbg_ns("%s: answer is NXDOMAIN\n", __func__);
-	case NODATA: /* NODATA or NXDOMAIN, append AUTHORITY SOA. */
-		ret = ns_put_authority_soa(pkt->zone->contents, pkt);
-		dbg_ns("%s: putting authority SOA = %d\n", __func__, ret);
-		break;
-	case DELEG:  /* Referral response. */ /*! \todo DS + NS */
-		ret = ns_referral(qdata->node, pkt->zone->contents, *qname, pkt, knot_pkt_qtype(pkt));
-		break;
-	case ERROR:
-		dbg_ns("%s: failed to resolve qname\n", __func__);
-		break;
-	default:
-		dbg_ns("%s: invalid state after qname processing = %d\n",
-		       __func__, state);
-		assert(0);
-		qdata->rcode = KNOT_RCODE_SERVFAIL;
-		break;
+	/* Set minimal supported size from EDNS(0). */
+	if (!(ctx->flags & NS_PKTSIZE_NOLIMIT)) {
+		uint16_t client_maxlen = knot_edns_get_payload(&query->opt_rr);
+		uint16_t server_maxlen = knot_edns_get_payload(&resp->opt_rr);
+		resp->max_size = MAX(resp->max_size, MIN(client_maxlen, server_maxlen));
 	}
 
+	dbg_ns("%s: packet size limit %zuB\n", __func__, resp->max_size);
 	return ret;
 }
-
-static int in_zone_answer(knot_pkt_t *resp, struct query_data *qdata)
-{
-	dbg_ns("%s(%p, %p)\n", __func__, resp, qdata);
-
-	/* Write answer RRs for QNAME. */
-	dbg_ns("%s: writing %p ANSWER\n", __func__, resp);
-	knot_pkt_begin(resp, KNOT_ANSWER);
-
-	const knot_dname_t *qname = knot_pkt_qname(resp);
-
-	/* Get answer to QNAME. */
-	int state = in_zone_solve_answer(&qname, resp, qdata);
-
-	/* Resolve AUTHORITY. */
-	dbg_ns("%s: writing %p AUTHORITY\n", __func__, resp);
-	knot_pkt_begin(resp, KNOT_AUTHORITY);
-	int ret = in_zone_solve_authority(state, &qname, resp, qdata);
-	if (ret != KNOT_EOK) {
-		return NS_PROC_FAIL;
-
-	}
-
-	// add all missing NSECs/NSEC3s for wildcard nodes
-	/*! \todo Make function accept query_data with zone+wcnodes */
-
-	/* Resolve ADDITIONAL. */
-	dbg_ns("%s: writing %p ADDITIONAL\n", __func__, resp);
-	knot_pkt_begin(resp, KNOT_ADDITIONAL);
-	ret = ns_put_additional(resp);
-	if (ret != KNOT_EOK) {
-		return NS_PROC_FAIL;
-
-	}
-
-	/* Write RCODE. */
-	knot_wire_set_rcode(resp->wire, qdata->rcode);
-
-	/* Complete response. */
-	return NS_PROC_FINISH;
-}
diff --git a/src/libknot/nameserver/ns_proc_query.h b/src/libknot/nameserver/ns_proc_query.h
index d7a4e2f268857676fbdebe0bd980608138bb7fd2..b03185815876e3afd414d46d15fb1bf01c39d3a8 100644
--- a/src/libknot/nameserver/ns_proc_query.h
+++ b/src/libknot/nameserver/ns_proc_query.h
@@ -3,9 +3,9 @@
  *
  * \author Marek Vavrusa <marek.vavrusa@nic.cz>
  *
- * \brief Normal query processor.
+ * \brief Query processor.
  *
- * \addtogroup libknot
+ * \addtogroup query_processing
  * @{
  */
 /*  Copyright (C) 2013 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
@@ -24,8 +24,8 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef _KNOT_NS_PROC_QUERY_H
-#define _KNOT_NS_PROC_QUERY_H
+#ifndef _KNOT_NS_PROC_QUERY_H_
+#define _KNOT_NS_PROC_QUERY_H_
 
 #include "libknot/nameserver/name-server.h"
 
@@ -33,6 +33,15 @@ extern const ns_proc_module_t _ns_proc_query;
 #define NS_PROC_QUERY (&_ns_proc_query)
 #define NS_PROC_QUERY_ID 1
 
+struct query_data {
+	uint16_t rcode;
+	uint16_t rcode_tsig;
+	knot_pkt_t *pkt;
+	const knot_node_t *node, *encloser, *previous;
+	list_t wildcards;
+	mm_ctx_t *mm;
+};
+
 int ns_proc_query_begin(ns_proc_context_t *ctx);
 int ns_proc_query_reset(ns_proc_context_t *ctx);
 int ns_proc_query_finish(ns_proc_context_t *ctx);
@@ -40,6 +49,6 @@ int ns_proc_query_in(knot_pkt_t *pkt, ns_proc_context_t *ctx);
 int ns_proc_query_out(knot_pkt_t *pkt, ns_proc_context_t *ctx);
 int ns_proc_query_err(knot_pkt_t *pkt, ns_proc_context_t *ctx);
 
-#endif /* _KNOT_NS_PROC_QUERY_H */
+#endif /* _KNOT_NS_PROC_QUERY_H_ */
 
 /*! @} */
diff --git a/src/libknot/zone/zone.c b/src/libknot/zone/zone.c
index c45849844889f69e1969c01cd689a202908ad07e..8dfa0075b7b9f5d72e71e1064c0ecc50d337f3ad 100644
--- a/src/libknot/zone/zone.c
+++ b/src/libknot/zone/zone.c
@@ -271,3 +271,15 @@ void knot_zone_set_flag(knot_zone_t *zone, knot_zone_flag_t flag, unsigned on)
 		}
 	}
 }
+
+int knot_zone_state(const knot_zone_t *zone)
+{
+	if (zone == NULL) {
+		dbg_ns("%s: zone not found\n", __func__);
+		return KNOT_ENOENT;
+	} else if (zone->contents == NULL) {
+		dbg_ns("%s: zone expired or stub\n", __func__);
+		return KNOT_ENOZONE;
+	}
+	return KNOT_EOK;
+}
diff --git a/src/libknot/zone/zone.h b/src/libknot/zone/zone.h
index 90cdf87fcdedfe53d3cb06040a8ef3deefd31926..08b493d5746ade638f74db7de30595cc2124387e 100644
--- a/src/libknot/zone/zone.h
+++ b/src/libknot/zone/zone.h
@@ -201,6 +201,15 @@ static inline unsigned knot_zone_flags(knot_zone_t *zone) {
  */
 void knot_zone_set_flag(knot_zone_t *zone, knot_zone_flag_t flag, unsigned on);
 
+/*!
+ * \brief Return zone state.
+ * \param zone Inspected zone.
+ * \retval KNOT_EOK if OK
+ * \retval KNOT_ENOENT if not exists
+ * \retval KNOT_ENOZONE if expired or stub
+ */
+int knot_zone_state(const knot_zone_t *zone);
+
 #endif
 
 /*! @} */